Ana2012 commited on
Commit
4402d23
·
verified ·
1 Parent(s): 88e30cb

Upload folder using huggingface_hub

Browse files
.dockerignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fly.toml
2
+ .git/
3
+ __pycache__/
4
+ .envrc
5
+ .venv/
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
36
+ data/products_tratado_textobusca.csv filter=lfs diff=lfs merge=lfs -text
ChatAmoOfertas/.gitattributes ADDED
@@ -0,0 +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
ChatAmoOfertas/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ChatAmoOfertas
3
+ emoji: 🏃
4
+ colorFrom: green
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.11-slim
3
+
4
+ RUN useradd -m -u 1000 user
5
+
6
+ WORKDIR /app
7
+
8
+ ENV PYTHONUNBUFFERED=1 \
9
+ HF_HOME=/home/user/.cache/huggingface
10
+
11
+ COPY requirements.txt requirements.txt
12
+ RUN pip install --no-cache-dir --upgrade pip \
13
+ && pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \
14
+ && pip install --no-cache-dir -r requirements.txt
15
+
16
+ COPY . /app
17
+ RUN chown -R user:user /app
18
+
19
+ USER user
20
+
21
+ EXPOSE 7860
22
+
23
+ CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (204 Bytes). View file
 
app/__pycache__/agent.cpython-313.pyc ADDED
Binary file (2.94 kB). View file
 
app/__pycache__/feedback.cpython-313.pyc ADDED
Binary file (2.5 kB). View file
 
app/__pycache__/logger.cpython-313.pyc ADDED
Binary file (2.92 kB). View file
 
app/__pycache__/main.cpython-313.pyc ADDED
Binary file (3.37 kB). View file
 
app/__pycache__/memory.cpython-313.pyc ADDED
Binary file (2.17 kB). View file
 
app/__pycache__/search.cpython-313.pyc ADDED
Binary file (13.9 kB). View file
 
app/__pycache__/test_agent.cpython-313.pyc ADDED
Binary file (819 Bytes). View file
 
app/__pycache__/utils.cpython-313.pyc ADDED
Binary file (3.01 kB). View file
 
app/agent.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .search import SearchEngine
2
+
3
+
4
+ class ShoppingAgent:
5
+ def __init__(self):
6
+ self.search_engine = SearchEngine()
7
+ self.search_engine.load()
8
+
9
+ def montar_resposta(self, query, resultados):
10
+ if not resultados:
11
+ return f'Não encontrei produtos relevantes para "{query}".'
12
+
13
+ nomes = [item["product_name"] for item in resultados[:3]]
14
+
15
+ if len(nomes) == 1:
16
+ return f'Encontrei um produto relevante para "{query}": {nomes[0]}.'
17
+
18
+ if len(nomes) == 2:
19
+ return f'Encontrei produtos relevantes para "{query}", com destaque para {nomes[0]} e {nomes[1]}.'
20
+
21
+ return (
22
+ f'Encontrei produtos relevantes para "{query}", com destaque para '
23
+ f'{nomes[0]}, {nomes[1]} e {nomes[2]}.'
24
+ )
25
+
26
+ def verificar_resposta(self, resposta, resultados):
27
+ if not resultados:
28
+ return resposta
29
+
30
+ nomes_resultados = [item["product_name"] for item in resultados]
31
+ resposta_limpa = resposta.lower()
32
+
33
+ mencoes_validas = any(nome.lower() in resposta_limpa for nome in nomes_resultados)
34
+
35
+ if mencoes_validas:
36
+ return resposta
37
+
38
+ top1 = resultados[0]["product_name"]
39
+ return f"{resposta} O item mais relevante encontrado foi {top1}."
40
+
41
+ def responder(self, query, top_k=5):
42
+ busca = self.search_engine.buscar(query, top_k=top_k)
43
+ resultados = busca["resultados"]
44
+
45
+ resposta_inicial = self.montar_resposta(query, resultados)
46
+ resposta_final = self.verificar_resposta(resposta_inicial, resultados)
47
+
48
+ return {
49
+ "query": query,
50
+ "categoria_inferida": busca["categoria_inferida"],
51
+ "answer": resposta_final,
52
+ "products": resultados,
53
+ }
app/feedback.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import os
3
+ from datetime import datetime
4
+
5
+ from .memory import salvar_memoria_negativa
6
+
7
+
8
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
9
+ LOGS_DIR = os.path.join(BASE_DIR, "logs")
10
+ FEEDBACK_FILE = os.path.join(LOGS_DIR, "feedback.csv")
11
+
12
+
13
+ def garantir_pasta_logs():
14
+ os.makedirs(LOGS_DIR, exist_ok=True)
15
+
16
+
17
+ def inicializar_arquivo_feedback():
18
+ garantir_pasta_logs()
19
+
20
+ if not os.path.exists(FEEDBACK_FILE):
21
+ with open(FEEDBACK_FILE, mode="w", newline="", encoding="utf-8") as f:
22
+ writer = csv.writer(f)
23
+ writer.writerow([
24
+ "timestamp",
25
+ "query",
26
+ "product_id",
27
+ "product_name",
28
+ "rating",
29
+ "is_helpful"
30
+ ])
31
+
32
+
33
+ def salvar_feedback(query, product_id, product_name, rating=None, is_helpful=None):
34
+ inicializar_arquivo_feedback()
35
+
36
+ with open(FEEDBACK_FILE, mode="a", newline="", encoding="utf-8") as f:
37
+ writer = csv.writer(f)
38
+ writer.writerow([
39
+ datetime.now().isoformat(),
40
+ query,
41
+ product_id,
42
+ product_name,
43
+ rating if rating is not None else "",
44
+ is_helpful if is_helpful is not None else ""
45
+ ])
46
+
47
+ # Regra simples para criar memória negativa
48
+ if rating is not None and rating <= 2:
49
+ salvar_memoria_negativa(
50
+ query=query,
51
+ product_id=product_id,
52
+ product_name=product_name,
53
+ rating=rating,
54
+ motivo="rating_baixo"
55
+ )
56
+
57
+ if is_helpful is False:
58
+ salvar_memoria_negativa(
59
+ query=query,
60
+ product_id=product_id,
61
+ product_name=product_name,
62
+ rating=rating if rating is not None else "",
63
+ motivo="nao_foi_util"
64
+ )
65
+
66
+ return {
67
+ "status": "ok",
68
+ "message": "Feedback salvo com sucesso."
69
+ }
app/logger.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import os
3
+ from datetime import datetime
4
+
5
+
6
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+ LOGS_DIR = os.path.join(BASE_DIR, "logs")
8
+ SEARCH_LOG_FILE = os.path.join(LOGS_DIR, "search_logs.csv")
9
+
10
+
11
+ def garantir_pasta_logs():
12
+ os.makedirs(LOGS_DIR, exist_ok=True)
13
+
14
+
15
+ def inicializar_arquivo_logs():
16
+ garantir_pasta_logs()
17
+
18
+ if not os.path.exists(SEARCH_LOG_FILE):
19
+ with open(SEARCH_LOG_FILE, "w", newline="", encoding="utf-8") as f:
20
+ writer = csv.writer(f)
21
+ writer.writerow([
22
+ "timestamp",
23
+ "query",
24
+ "categoria_inferida",
25
+ "answer",
26
+ "top1_id",
27
+ "top1_name",
28
+ "top2_id",
29
+ "top2_name",
30
+ "top3_id",
31
+ "top3_name"
32
+ ])
33
+
34
+
35
+ def salvar_log_busca(resultado):
36
+ inicializar_arquivo_logs()
37
+
38
+ produtos = resultado.get("products", [])
39
+
40
+ def get_prod(i, campo):
41
+ if i < len(produtos):
42
+ return produtos[i].get(campo, "")
43
+ return ""
44
+
45
+ with open(SEARCH_LOG_FILE, "a", newline="", encoding="utf-8") as f:
46
+ writer = csv.writer(f)
47
+ writer.writerow([
48
+ datetime.now().isoformat(),
49
+ resultado.get("query", ""),
50
+ resultado.get("categoria_inferida", ""),
51
+ resultado.get("answer", ""),
52
+ get_prod(0, "product_id"),
53
+ get_prod(0, "product_name"),
54
+ get_prod(1, "product_id"),
55
+ get_prod(1, "product_name"),
56
+ get_prod(2, "product_id"),
57
+ get_prod(2, "product_name"),
58
+ ])
59
+
60
+ return {"status": "ok"}
app/main.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import threading
3
+
4
+ from fastapi import FastAPI, Response
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from fastapi.responses import RedirectResponse
7
+ from pydantic import BaseModel
8
+ from typing import Optional
9
+
10
+ from .agent import ShoppingAgent
11
+ from .feedback import salvar_feedback
12
+ from .logger import salvar_log_busca
13
+
14
+
15
+ def _cors_origins():
16
+ base = [
17
+ "http://127.0.0.1:5173",
18
+ "http://localhost:5173",
19
+ "http://127.0.0.1:3000",
20
+ "http://localhost:3000",
21
+ ]
22
+ extra = os.getenv("CORS_ORIGINS", "")
23
+ if extra.strip():
24
+ base.extend(o.strip() for o in extra.split(",") if o.strip())
25
+ return base
26
+
27
+
28
+ app = FastAPI(title="TCC2 Agent API")
29
+
30
+ app.add_middleware(
31
+ CORSMiddleware,
32
+ allow_origins=_cors_origins(),
33
+ allow_origin_regex=r"https://.*\.hf\.space$",
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ agent = None
40
+ agent_lock = threading.Lock()
41
+
42
+
43
+ def get_agent():
44
+ global agent
45
+ if agent is None:
46
+ with agent_lock:
47
+ if agent is None:
48
+ agent = ShoppingAgent()
49
+ return agent
50
+
51
+
52
+ class ChatRequest(BaseModel):
53
+ query: Optional[str] = None
54
+ message: Optional[str] = None
55
+ top_k: int = 5
56
+
57
+
58
+ class FeedbackRequest(BaseModel):
59
+ query: str
60
+ product_id: str
61
+ product_name: str
62
+ rating: Optional[int] = None
63
+ is_helpful: Optional[bool] = None
64
+
65
+
66
+ @app.get("/health")
67
+ def health():
68
+ return {"status": "ok", "agent_ready": agent is not None}
69
+
70
+
71
+ @app.get("/", include_in_schema=False)
72
+ def root():
73
+ return RedirectResponse(url="/docs")
74
+
75
+
76
+ @app.get("/favicon.ico", include_in_schema=False)
77
+ def favicon():
78
+ return Response(status_code=204)
79
+
80
+
81
+ @app.post("/chat")
82
+ def chat(request: ChatRequest):
83
+ texto = request.query or request.message
84
+
85
+ if not texto:
86
+ return {"error": "query ou message deve ser informado"}
87
+
88
+ resultado = get_agent().responder(texto, top_k=request.top_k)
89
+ salvar_log_busca(resultado)
90
+ return resultado
91
+
92
+
93
+ @app.post("/feedback")
94
+ def feedback(request: FeedbackRequest):
95
+ return salvar_feedback(
96
+ query=request.query,
97
+ product_id=request.product_id,
98
+ product_name=request.product_name,
99
+ rating=request.rating,
100
+ is_helpful=request.is_helpful
101
+ )
app/memory.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import os
3
+ from datetime import datetime
4
+
5
+
6
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+ LOGS_DIR = os.path.join(BASE_DIR, "logs")
8
+ NEGATIVE_MEMORY_FILE = os.path.join(LOGS_DIR, "negative_memory.csv")
9
+
10
+
11
+ def garantir_pasta_logs():
12
+ os.makedirs(LOGS_DIR, exist_ok=True)
13
+
14
+
15
+ def inicializar_memoria_negativa():
16
+ garantir_pasta_logs()
17
+
18
+ if not os.path.exists(NEGATIVE_MEMORY_FILE):
19
+ with open(NEGATIVE_MEMORY_FILE, "w", newline="", encoding="utf-8") as f:
20
+ writer = csv.writer(f)
21
+ writer.writerow([
22
+ "timestamp",
23
+ "query",
24
+ "product_id",
25
+ "product_name",
26
+ "rating",
27
+ "motivo"
28
+ ])
29
+
30
+
31
+ def salvar_memoria_negativa(query, product_id, product_name, rating, motivo="feedback_negativo"):
32
+ inicializar_memoria_negativa()
33
+
34
+ with open(NEGATIVE_MEMORY_FILE, "a", newline="", encoding="utf-8") as f:
35
+ writer = csv.writer(f)
36
+ writer.writerow([
37
+ datetime.now().isoformat(),
38
+ query,
39
+ product_id,
40
+ product_name,
41
+ rating,
42
+ motivo
43
+ ])
44
+
45
+ return {"status": "ok"}
app/search.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ import torch
5
+ from transformers import AutoTokenizer, AutoModel
6
+ from sklearn.metrics.pairwise import cosine_similarity
7
+
8
+ from .utils import (
9
+ limpar_texto,
10
+ mapear_categoria,
11
+ inferir_categoria_consulta,
12
+ bonus_lexical,
13
+ )
14
+
15
+
16
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ DATA_DIR = os.path.join(BASE_DIR, "data")
18
+ LOGS_DIR = os.path.join(BASE_DIR, "logs")
19
+
20
+ PATH_PRODUCTS = os.path.join(DATA_DIR, "products_tratado_textobusca.csv")
21
+ PATH_EMBEDDINGS = os.path.join(DATA_DIR, "embeddings_produtos_bertimbau_reforcado.npy")
22
+ PATH_NEGATIVE_MEMORY = os.path.join(LOGS_DIR, "negative_memory.csv")
23
+
24
+ MODEL_NAME = "neuralmind/bert-base-portuguese-cased"
25
+
26
+
27
+ class SearchEngine:
28
+ def __init__(self):
29
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
30
+ self.tokenizer = None
31
+ self.model = None
32
+ self.df_produtos = None
33
+ self.emb_produtos = None
34
+ self.df_negative_memory = pd.DataFrame()
35
+ self.negative_memory_mtime = None
36
+
37
+ def load(self):
38
+ self._load_products()
39
+ self._load_model()
40
+ self._load_embeddings()
41
+ self._refresh_negative_memory(force=True)
42
+
43
+ def _load_products(self):
44
+ df = pd.read_csv(PATH_PRODUCTS)
45
+ df.columns = df.columns.str.strip().str.lower()
46
+
47
+ df["product_name"] = df["product_name"].fillna("").astype(str)
48
+ df["description"] = df["description"].fillna("").astype(str)
49
+ df["categoria_principal"] = df["categoria_principal"].fillna("").astype(str)
50
+ df["category_names_text"] = df["category_names_text"].fillna("").astype(str)
51
+ df["region"] = df["region"].fillna("").astype(str)
52
+ df["neighborhood"] = df["neighborhood"].fillna("").astype(str)
53
+
54
+ df["product_name_limpo"] = df["product_name"].apply(limpar_texto)
55
+ df["description_limpa"] = df["description"].apply(limpar_texto)
56
+ df["categoria_principal_limpa"] = df["categoria_principal"].apply(limpar_texto)
57
+ df["category_names_text_limpo"] = df["category_names_text"].apply(limpar_texto)
58
+ df["region_limpa"] = df["region"].apply(limpar_texto)
59
+ df["neighborhood_limpo"] = df["neighborhood"].apply(limpar_texto)
60
+
61
+ df["texto_busca_reforcado"] = (
62
+ "produto " + df["product_name_limpo"] + " "
63
+ + "categoria " + df["categoria_principal_limpa"] + " "
64
+ + "categorias " + df["category_names_text_limpo"] + " "
65
+ + "bairro " + df["neighborhood_limpo"] + " "
66
+ + "regiao " + df["region_limpa"] + " "
67
+ + "descricao " + df["description_limpa"]
68
+ ).str.strip()
69
+
70
+ df["categoria_grupo"] = df["categoria_principal"].apply(mapear_categoria)
71
+
72
+ self.df_produtos = df
73
+
74
+ def _load_model(self):
75
+ self.tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
76
+ self.model = AutoModel.from_pretrained(MODEL_NAME)
77
+ self.model = self.model.to(self.device)
78
+ self.model.eval()
79
+
80
+ def _load_embeddings(self):
81
+ self.emb_produtos = np.load(PATH_EMBEDDINGS)
82
+
83
+ def _refresh_negative_memory(self, force=False):
84
+ if not os.path.exists(PATH_NEGATIVE_MEMORY):
85
+ self.df_negative_memory = pd.DataFrame()
86
+ self.negative_memory_mtime = None
87
+ return
88
+
89
+ current_mtime = os.path.getmtime(PATH_NEGATIVE_MEMORY)
90
+ if not force and self.negative_memory_mtime == current_mtime:
91
+ return
92
+
93
+ df = pd.read_csv(PATH_NEGATIVE_MEMORY)
94
+ df.columns = df.columns.str.strip().str.lower()
95
+
96
+ for col in ["query", "product_id", "product_name", "motivo", "rating"]:
97
+ if col not in df.columns:
98
+ df[col] = ""
99
+
100
+ df["query"] = df["query"].fillna("").astype(str)
101
+ df["query_limpa"] = df["query"].apply(limpar_texto)
102
+ df["product_id"] = df["product_id"].fillna("").astype(str)
103
+ df["product_name"] = df["product_name"].fillna("").astype(str)
104
+ df["motivo"] = df["motivo"].fillna("").astype(str)
105
+ df["rating_num"] = pd.to_numeric(df["rating"], errors="coerce")
106
+
107
+ self.df_negative_memory = df
108
+ self.negative_memory_mtime = current_mtime
109
+
110
+ def _similaridade_consulta(self, query_atual, query_memoria):
111
+ if not query_atual or not query_memoria:
112
+ return 0.0
113
+
114
+ if query_atual == query_memoria:
115
+ return 1.0
116
+
117
+ termos_atuais = set(query_atual.split())
118
+ termos_memoria = set(query_memoria.split())
119
+
120
+ if not termos_atuais or not termos_memoria:
121
+ return 0.0
122
+
123
+ intersecao = len(termos_atuais & termos_memoria)
124
+ if intersecao == 0:
125
+ return 0.0
126
+
127
+ return intersecao / max(len(termos_atuais), len(termos_memoria))
128
+
129
+ def _calcular_penalidade_feedback(self, query_text, df_filtrado):
130
+ self._refresh_negative_memory()
131
+
132
+ if self.df_negative_memory.empty:
133
+ return np.zeros(len(df_filtrado))
134
+
135
+ query_limpa = limpar_texto(query_text)
136
+ memorias = self.df_negative_memory[
137
+ self.df_negative_memory["product_id"].isin(df_filtrado["product_id"].astype(str))
138
+ ]
139
+
140
+ if memorias.empty:
141
+ return np.zeros(len(df_filtrado))
142
+
143
+ penalidades = {}
144
+
145
+ for _, memoria in memorias.iterrows():
146
+ similaridade = self._similaridade_consulta(query_limpa, memoria["query_limpa"])
147
+ if similaridade <= 0:
148
+ continue
149
+
150
+ penalidade = 0.08 + (0.12 * similaridade)
151
+
152
+ if memoria["motivo"] == "nao_foi_util":
153
+ penalidade += 0.04
154
+
155
+ if pd.notna(memoria["rating_num"]) and memoria["rating_num"] <= 2:
156
+ penalidade += 0.04
157
+
158
+ product_id = memoria["product_id"]
159
+ penalidades[product_id] = min(penalidades.get(product_id, 0.0) + penalidade, 0.45)
160
+
161
+ return df_filtrado["product_id"].astype(str).map(lambda x: penalidades.get(x, 0.0)).values
162
+
163
+ def gerar_embedding_unico(self, texto, max_length=64):
164
+ inputs = self.tokenizer(
165
+ texto,
166
+ padding=True,
167
+ truncation=True,
168
+ max_length=max_length,
169
+ return_tensors="pt"
170
+ )
171
+
172
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
173
+
174
+ with torch.no_grad():
175
+ outputs = self.model(**inputs)
176
+
177
+ last_hidden_state = outputs.last_hidden_state
178
+ attention_mask = inputs["attention_mask"].unsqueeze(-1)
179
+
180
+ soma = torch.sum(last_hidden_state * attention_mask, dim=1)
181
+ divisor = torch.clamp(attention_mask.sum(dim=1), min=1e-9)
182
+ embedding = soma / divisor
183
+
184
+ return embedding.cpu().numpy()[0]
185
+
186
+ def buscar(self, query_text, top_k=5):
187
+ query_limpa = limpar_texto(query_text)
188
+ categoria = inferir_categoria_consulta(query_limpa)
189
+
190
+ if categoria is not None:
191
+ mask = self.df_produtos["categoria_grupo"] == categoria
192
+ df_filtrado = self.df_produtos[mask].copy()
193
+ idx_filtrado = df_filtrado.index.tolist()
194
+ else:
195
+ df_filtrado = self.df_produtos.copy()
196
+ idx_filtrado = df_filtrado.index.tolist()
197
+
198
+ if len(df_filtrado) == 0:
199
+ df_filtrado = self.df_produtos.copy()
200
+ idx_filtrado = df_filtrado.index.tolist()
201
+
202
+ emb_query = self.gerar_embedding_unico(query_text).reshape(1, -1)
203
+ emb_base = self.emb_produtos[idx_filtrado]
204
+
205
+ sims = cosine_similarity(emb_query, emb_base)[0]
206
+
207
+ bonus = df_filtrado.apply(
208
+ lambda row: bonus_lexical(
209
+ query_text,
210
+ row["product_name"],
211
+ row["categoria_principal"],
212
+ row["neighborhood"],
213
+ row["region"],
214
+ ),
215
+ axis=1,
216
+ ).values
217
+ penalidade_feedback = self._calcular_penalidade_feedback(query_text, df_filtrado)
218
+ score_final = sims + bonus - penalidade_feedback
219
+
220
+ top_idx_local = np.argsort(score_final)[::-1][:top_k]
221
+
222
+ resultados = []
223
+ for rank, idx_local in enumerate(top_idx_local, start=1):
224
+ idx_global = idx_filtrado[idx_local]
225
+ prod = self.df_produtos.iloc[idx_global]
226
+
227
+ resultados.append({
228
+ "rank": rank,
229
+ "establishment_id": str(prod["establishment_id"]),
230
+ "product_id": str(prod["product_id"]),
231
+ "product_name": prod["product_name"],
232
+ "categoria_principal": prod["categoria_principal"],
233
+ "categoria_grupo": prod["categoria_grupo"],
234
+ "region": prod["region"],
235
+ "neighborhood": prod["neighborhood"],
236
+ "score_semantico": float(sims[idx_local]),
237
+ "bonus_lexical": float(bonus[idx_local]),
238
+ "penalidade_feedback": float(penalidade_feedback[idx_local]),
239
+ "score_final": float(score_final[idx_local]),
240
+ })
241
+
242
+ return {
243
+ "query": query_text,
244
+ "categoria_inferida": categoria,
245
+ "resultados": resultados,
246
+ }
app/test_agent.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .agent import ShoppingAgent
2
+
3
+ agent = ShoppingAgent()
4
+
5
+ resultado = agent.responder("coca cola 2l")
6
+
7
+ print("Consulta:", resultado["query"])
8
+ print("Categoria inferida:", resultado["categoria_inferida"])
9
+ print("Resposta do agente:", resultado["answer"])
10
+ print("\nProdutos encontrados:")
11
+
12
+ for item in resultado["products"]:
13
+ print(
14
+ item["rank"],
15
+ item["product_name"],
16
+ item["categoria_principal"],
17
+ item["score_final"]
18
+ )
app/test_search.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .search import SearchEngine
2
+
3
+ engine = SearchEngine()
4
+ engine.load()
5
+
6
+ resultado = engine.buscar("coca cola 2l", top_k=5)
7
+
8
+ print("Consulta:", resultado["query"])
9
+ print("Categoria inferida:", resultado["categoria_inferida"])
10
+
11
+ for item in resultado["resultados"]:
12
+ print(
13
+ item["rank"],
14
+ item["product_name"],
15
+ item["categoria_principal"],
16
+ item["score_final"]
17
+ )
app/utils.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import unicodedata
3
+ import pandas as pd
4
+
5
+
6
+ def limpar_texto(texto):
7
+ if pd.isna(texto):
8
+ return ""
9
+
10
+ texto = str(texto).lower().strip()
11
+ texto = unicodedata.normalize("NFKD", texto)
12
+ texto = "".join(c for c in texto if not unicodedata.combining(c))
13
+
14
+ texto = re.sub(r"[\n\r\t]", " ", texto)
15
+ texto = re.sub(r"[^a-z0-9\s]", " ", texto)
16
+ texto = re.sub(r"\s+", " ", texto).strip()
17
+
18
+ return texto
19
+
20
+
21
+ def mapear_categoria(cat):
22
+ cat = limpar_texto(cat)
23
+
24
+ if "acai" in cat:
25
+ return "acai"
26
+ if "pastel" in cat or "pastel de pizza" in cat:
27
+ return "pastel"
28
+ if "pizza" in cat:
29
+ return "pizza"
30
+ if "hamburg" in cat or "burger" in cat:
31
+ return "hamburguer"
32
+ if "sushi" in cat or "japones" in cat or "oriental" in cat:
33
+ return "japones"
34
+ if "suco" in cat:
35
+ return "suco"
36
+ if "bebida" in cat or "refrigerante" in cat or "refri" in cat:
37
+ return "bebida"
38
+
39
+ return cat
40
+
41
+
42
+ def inferir_categoria_consulta(query):
43
+ q = limpar_texto(query)
44
+
45
+ if "acai" in q:
46
+ return "acai"
47
+ if "pastel" in q or "pastel de pizza" in q:
48
+ return "pastel"
49
+ if "pizza" in q:
50
+ return "pizza"
51
+ if "hamburguer" in q or "burger" in q or "x bacon" in q:
52
+ return "hamburguer"
53
+ if "sushi" in q or "temaki" in q:
54
+ return "japones"
55
+ if "suco" in q:
56
+ return "suco"
57
+ if "coca" in q or "refrigerante" in q or "refri" in q:
58
+ return "bebida"
59
+
60
+ return None
61
+
62
+
63
+ def bonus_lexical(query, *texts):
64
+ q = limpar_texto(query)
65
+ referencias = [limpar_texto(texto) for texto in texts if texto]
66
+
67
+ bonus = 0.0
68
+
69
+ for termo in q.split():
70
+ if any(termo in referencia for referencia in referencias):
71
+ bonus += 0.03
72
+
73
+ return bonus
data/embeddings_produtos_bertimbau_reforcado.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fd9acd51ae3ccf45d25108f07c4aa51c662ed9c77f38a728c0853199152687ed
3
+ size 158850176
data/products_tratado_textobusca.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4fd942f41249a721b2342e5a72b8ab0c3a2799ba8e0fe4b78732068c0f7b10ed
3
+ size 31993441
fly.toml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # fly.toml app configuration file generated for backend-damp-fog-5601 on 2026-03-26T21:56:01-03:00
2
+ #
3
+ # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4
+ #
5
+
6
+ app = 'backend-damp-fog-5601'
7
+ primary_region = 'gru'
8
+
9
+ [build]
10
+ dockerfile = 'Dockerfile'
11
+
12
+ [processes]
13
+ app = 'uvicorn app.main:app --host 0.0.0.0 --port 7860'
14
+
15
+ [http_service]
16
+ internal_port = 7860
17
+ force_https = true
18
+ auto_stop_machines = 'stop'
19
+ auto_start_machines = true
20
+ min_machines_running = 0
21
+ processes = ['app']
22
+
23
+ [[vm]]
24
+ memory = '1gb'
25
+ cpus = 1
26
+ memory_mb = 1024
logs/feedback.csv ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ timestamp,query,product_id,product_name,rating,is_helpful
2
+ 2026-03-13T22:00:36.405774,coca cola 2l,63ff69eb13a17c00145f829,Coca Cola Zero 2L,5,True
3
+ 2026-03-13T22:26:50.893492,açaí 300ml,teste123,Suco de Manga 300ml,1,False
4
+ 2026-03-13T22:28:15.721470,Pastel Pizza,66ed93e747c5ef68704c2fce,Mini pizza - calabresa com queijo,1,False
5
+ 2026-03-16T19:32:17.749606,Pastel de Pizza,60c52520a0ec340012bfc4fa,Pizza de Frango com catupiry,1,False
6
+ 2026-03-16T19:32:38.592032,Pastel de pizza,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela,1,False
7
+ 2026-03-16T19:33:23.320661,Açai com granola,5ba15a333ac958000f91cebf,Açaí 400ml com Frutas + Granola + Leite em Pó,4,True
8
+ 2026-03-16T19:34:48.503286,Agua,66fc4ef3721f412449424f8e,Água Crystal sem Gás 500ml,5,True
9
+ 2026-03-16T19:34:59.316041,Agua com gás,61f3f617379ac90012fed81b,ÁGUA COM GÁS,5,True
10
+ 2026-03-16T19:47:15.039150,Açai na efapi,5fe0c582904d350012f1cefb,Navio de Açaí 1kg,5,True
11
+ 2026-03-16T19:52:37.398685,Acai na efapi,64e1229bf82b840017dd9615,Smothie 500ml,3,False
12
+ 2026-03-16T19:53:25.907009,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,False
13
+ 2026-03-16T19:59:25.873139,PAstel de Pizza,60b67d0b051001001221b236,Pizza gigante com borda de catupiry,1,False
14
+ 2026-03-16T20:01:54.281600,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,False
15
+ 2026-03-16T20:02:47.926527,Coca cola na efapi,5ec594f4fdc27700121776e9,Coca-Cola 2L - Efapi,4,True
16
+ 2026-03-16T20:03:53.848469,acai com nutella,69307a2462a1f6ab53263bb2,Geladinho Gourmet Ninho com Nutella,4,True
17
+ 2026-03-16T20:04:27.371157,agua com gas,61f3f617379ac90012fed81b,ÁGUA COM GÁS,5,True
18
+ 2026-03-16T20:04:53.684421,pizza grande de calabressa,609ae76d2236c200139f1578,Calabresa,4,True
19
+ 2026-03-16T20:05:03.430851,pizza media,5dc9b94825bf860011280704,Pizza Média,4,True
20
+ 2026-03-16T20:05:35.168375,sorvete,617f089ca1260b0013e3b8e2,"Pote Sorvete Dielo Mania Abacaxi, Creme e Leite Condensado 2lt",5,True
21
+ 2026-03-16T20:05:42.649555,sorvete,617f089ca1260b0013e3b8e2,"Pote Sorvete Dielo Mania Abacaxi, Creme e Leite Condensado 2lt",5,True
22
+ 2026-03-16T20:09:20.078846,Pastel de Pizza,609ae76d2236c200139f1578,Calabresa,1,False
23
+ 2026-03-16T20:09:31.057960,Pastel de Pizza,60c5240bbd2ba700137a9876,Pizza de Calabresa,1,False
24
+ 2026-03-16T20:09:40.575439,Pastel,60788ce43ce2d000142dd661,CARNE COM CATUPIRY,5,True
25
+ 2026-03-16T20:09:49.890794,Pastel de Pizza,60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão,1,False
26
+ 2026-03-16T20:10:34.910584,Pastel de calabresa,688bcf37691a3584055bbe4a,Pastel frito de Calabresa com Queijo,3,False
27
+ 2026-03-16T20:10:46.959107,Pastel de frango,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,5,True
28
+ 2026-03-16T20:16:22.373439,Frango assado,6081c8b4725f9c0019afdb83,Frango Recheado com Bacon e Mandioca,4,True
29
+ 2026-03-16T20:16:57.740224,Salgados,67797f924a96dd7edc3b388f,Assado Integral de Brócolis,5,True
30
+ 2026-03-16T20:17:21.734493,cento de salgados,67797f924a96dd7edc3b388f,Assado Integral de Brócolis,3,False
31
+ 2026-03-16T20:17:26.535957,cento de salgados,67797f924a96dd7edc3b388f,Assado Integral de Brócolis,3,False
32
+ 2026-03-16T20:24:21.716257,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,False
33
+ 2026-03-16T20:24:24.614427,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,False
34
+ 2026-03-16T20:24:34.378088,PAstel de Pizza,608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),1,False
35
+ 2026-03-16T20:26:39.933595,Pastel de pizza,5f295637f76c8b001292d7bb,Pastel de Pizza,5,True
36
+ 2026-03-21T19:47:08.660294,eu não quero borda,62e1464ace20d10013d16d6f,"Caixinha ""Quero morar no seu abraço...""",1,False
37
+ 2026-03-21T19:47:53.873246,quero açai com cento de salgados e refri,6890ea99434eb6137e3f7636,25 Croquetes de Calabresa com Queijo,2,False
38
+ 2026-03-21T19:48:40.235451,quero massa carbonara,5ecbf6174eb0670012300212,Massa - Carbonara,5,True
39
+ 2026-03-21T19:49:02.769945,quero massa carbonara,5ecbf6174eb0670012300212,Massa - Carbonara,5,True
40
+ 2026-03-21T19:49:04.048930,quero massa carbonara,5ecbf6174eb0670012300212,Massa - Carbonara,5,True
logs/negative_memory.csv ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ timestamp,query,product_id,product_name,rating,motivo
2
+ 2026-03-13T22:26:50.894743,açaí 300ml,teste123,Suco de Manga 300ml,1,rating_baixo
3
+ 2026-03-13T22:26:50.895350,açaí 300ml,teste123,Suco de Manga 300ml,1,nao_foi_util
4
+ 2026-03-13T22:28:15.722159,Pastel Pizza,66ed93e747c5ef68704c2fce,Mini pizza - calabresa com queijo,1,rating_baixo
5
+ 2026-03-13T22:28:15.722843,Pastel Pizza,66ed93e747c5ef68704c2fce,Mini pizza - calabresa com queijo,1,nao_foi_util
6
+ 2026-03-16T19:32:17.750852,Pastel de Pizza,60c52520a0ec340012bfc4fa,Pizza de Frango com catupiry,1,rating_baixo
7
+ 2026-03-16T19:32:17.751503,Pastel de Pizza,60c52520a0ec340012bfc4fa,Pizza de Frango com catupiry,1,nao_foi_util
8
+ 2026-03-16T19:32:38.592998,Pastel de pizza,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela,1,rating_baixo
9
+ 2026-03-16T19:32:38.593966,Pastel de pizza,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela,1,nao_foi_util
10
+ 2026-03-16T19:52:37.400956,Acai na efapi,64e1229bf82b840017dd9615,Smothie 500ml,3,nao_foi_util
11
+ 2026-03-16T19:53:25.908557,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,rating_baixo
12
+ 2026-03-16T19:53:25.909841,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,nao_foi_util
13
+ 2026-03-16T19:59:25.874232,PAstel de Pizza,60b67d0b051001001221b236,Pizza gigante com borda de catupiry,1,rating_baixo
14
+ 2026-03-16T19:59:25.875325,PAstel de Pizza,60b67d0b051001001221b236,Pizza gigante com borda de catupiry,1,nao_foi_util
15
+ 2026-03-16T20:01:54.283055,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,rating_baixo
16
+ 2026-03-16T20:01:54.284256,Pastel de pizza,609bbf087a23ed0012fe6494,Strogonoff de Filé,1,nao_foi_util
17
+ 2026-03-16T20:09:20.079978,Pastel de Pizza,609ae76d2236c200139f1578,Calabresa,1,rating_baixo
18
+ 2026-03-16T20:09:20.080705,Pastel de Pizza,609ae76d2236c200139f1578,Calabresa,1,nao_foi_util
19
+ 2026-03-16T20:09:31.058691,Pastel de Pizza,60c5240bbd2ba700137a9876,Pizza de Calabresa,1,rating_baixo
20
+ 2026-03-16T20:09:31.059363,Pastel de Pizza,60c5240bbd2ba700137a9876,Pizza de Calabresa,1,nao_foi_util
21
+ 2026-03-16T20:09:49.891467,Pastel de Pizza,60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão,1,rating_baixo
22
+ 2026-03-16T20:09:49.892168,Pastel de Pizza,60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão,1,nao_foi_util
23
+ 2026-03-16T20:10:34.911304,Pastel de calabresa,688bcf37691a3584055bbe4a,Pastel frito de Calabresa com Queijo,3,nao_foi_util
24
+ 2026-03-16T20:17:21.735269,cento de salgados,67797f924a96dd7edc3b388f,Assado Integral de Brócolis,3,nao_foi_util
25
+ 2026-03-16T20:17:26.536626,cento de salgados,67797f924a96dd7edc3b388f,Assado Integral de Brócolis,3,nao_foi_util
26
+ 2026-03-16T20:24:21.716996,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,rating_baixo
27
+ 2026-03-16T20:24:21.717672,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,nao_foi_util
28
+ 2026-03-16T20:24:24.615191,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,rating_baixo
29
+ 2026-03-16T20:24:24.615919,Pastel de queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,1,nao_foi_util
30
+ 2026-03-16T20:24:34.378831,PAstel de Pizza,608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),1,rating_baixo
31
+ 2026-03-16T20:24:34.379620,PAstel de Pizza,608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),1,nao_foi_util
32
+ 2026-03-21T19:47:08.664282,eu não quero borda,62e1464ace20d10013d16d6f,"Caixinha ""Quero morar no seu abraço...""",1,rating_baixo
33
+ 2026-03-21T19:47:08.665675,eu não quero borda,62e1464ace20d10013d16d6f,"Caixinha ""Quero morar no seu abraço...""",1,nao_foi_util
34
+ 2026-03-21T19:47:53.875788,quero açai com cento de salgados e refri,6890ea99434eb6137e3f7636,25 Croquetes de Calabresa com Queijo,2,rating_baixo
35
+ 2026-03-21T19:47:53.877860,quero açai com cento de salgados e refri,6890ea99434eb6137e3f7636,25 Croquetes de Calabresa com Queijo,2,nao_foi_util
logs/search_logs.csv ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ timestamp,query,categoria_inferida,answer,top1_id,top1_name,top2_id,top2_name,top3_id,top3_name
2
+ 2026-03-13T22:16:17.618001,pizza calabresa,pizza,"Encontrei produtos relevantes para ""pizza calabresa"", com destaque para Pizza Sachet de Calabresa, Sandupizza Calabresa e Pizza Sachet de Calabresa.",5ecbbfe54eb06700122ffc04,Pizza Sachet de Calabresa,6207f1831a837500140b5a0d,Sandupizza Calabresa,64a9c66c6c39a2001287246b,Pizza Sachet de Calabresa
3
+ 2026-03-13T22:27:30.535304,Pastel Pizza,pizza,"Encontrei produtos relevantes para ""Pastel Pizza"", com destaque para Mini pizza - calabresa com queijo, 3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo) e Combo: Pizza + Fritas + Refri.",66ed93e747c5ef68704c2fce,Mini pizza - calabresa com queijo,608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),6237996c0845630012d425d2,Combo: Pizza + Fritas + Refri
4
+ 2026-03-16T19:31:25.586487,Coca cola 2L,bebida,"Encontrei produtos relevantes para ""Coca cola 2L"", com destaque para Coca-Cola 2L, Coca-Cola 2L e Coca Cola Zero 2L.",64bad8578ee0af001451efd9,Coca-Cola 2L,641f5e144f6c3800149a5cbe,Coca-Cola 2L,63ff69eb13a17c0014f5f829,Coca Cola Zero 2L
5
+ 2026-03-16T19:32:07.604101,Pastel de Pizza,pizza,"Encontrei produtos relevantes para ""Pastel de Pizza"", com destaque para Pizza de Frango com catupiry, Pizza gigante com borda de catupiry e Pizza Sachet de Muçarela.",60c52520a0ec340012bfc4fa,Pizza de Frango com catupiry,60b67d0b051001001221b236,Pizza gigante com borda de catupiry,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela
6
+ 2026-03-16T19:32:32.404418,Pastel de pizza,pizza,"Encontrei produtos relevantes para ""Pastel de pizza"", com destaque para Pizza Sachet de Muçarela, Pizza Sachet de Bacon e Pizza Sachet de Milho com Bacon.",64a9c54149ba5400137b7624,Pizza Sachet de Muçarela,5ecbbf544eb06700122ffbe4,Pizza Sachet de Bacon,64a9c57649ba5400137b8216,Pizza Sachet de Milho com Bacon
7
+ 2026-03-16T19:33:10.913396,Açai com granola,acai,"Encontrei produtos relevantes para ""Açai com granola"", com destaque para Açaí 400ml com Frutas + Granola + Leite em Pó, 🥰😋 Copo de Açaí Com Leite Condensado + Amendoim + Granola + Banana 480ml 🥰😋 e Copo Açaí Tradicional 300ml com Leite Condensado, Granola, Leite em Pó e Morango + Água s/ Gás.",5ba15a333ac958000f91cebf,Açaí 400ml com Frutas + Granola + Leite em Pó,5fde517cdf58000012999f87,🥰😋 Copo de Açaí Com Leite Condensado + Amendoim + Granola + Banana 480ml 🥰😋,64e1229af82b840017dd944c,"Copo Açaí Tradicional 300ml com Leite Condensado, Granola, Leite em Pó e Morango + Água s/ Gás"
8
+ 2026-03-16T19:33:52.342361,Agua,,"Encontrei produtos relevantes para ""Agua"", com destaque para Água Crystal sem Gás 500ml, Água Crystal sem Gás 500ml e Água Crystal com Gás 500ml.",66fc4ef3721f412449424f8e,Água Crystal sem Gás 500ml,65b3eb9b132349948d1728c2,Água Crystal sem Gás 500ml,65b3eb9b132349948d1728c6,Água Crystal com Gás 500ml
9
+ 2026-03-16T19:34:55.325249,Agua com gás,,"Encontrei produtos relevantes para ""Agua com gás"", com destaque para ÁGUA COM GÁS, Agua Mineral com Gás e Água com Gás 500ml.",61f3f617379ac90012fed81b,ÁGUA COM GÁS,63922cd410245d001345721d,Agua Mineral com Gás,5ecd214f5dc36a00123268fe,Água com Gás 500ml
10
+ 2026-03-16T19:46:50.352190,Coca cola 2L,bebida,"Encontrei produtos relevantes para ""Coca cola 2L"", com destaque para Coca-Cola 2L, Coca-Cola 2L e Coca Cola Zero 2L.",64bad8578ee0af001451efd9,Coca-Cola 2L,641f5e144f6c3800149a5cbe,Coca-Cola 2L,63ff69eb13a17c0014f5f829,Coca Cola Zero 2L
11
+ 2026-03-16T19:47:02.747610,Açai na efapi,acai,"Encontrei produtos relevantes para ""Açai na efapi"", com destaque para Navio de Açaí 1kg, Barca de Açai P e Smothie 500ml.",5fe0c582904d350012f1cefb,Navio de Açaí 1kg,688bcf37691a3584055bbd56,Barca de Açai P,64e1229bf82b840017dd9615,Smothie 500ml
12
+ 2026-03-16T19:50:21.482021,Açai no centro,acai,"Encontrei produtos relevantes para ""Açai no centro"", com destaque para Mix, Sorvete napolitano 2L 😋 🤩 e Açaí.",682247058d086e34fd9743c9,Mix,6476517bf605a8001336802c,Sorvete napolitano 2L 😋 🤩,682247058d086e34fd974408,Açaí
13
+ 2026-03-16T19:50:42.580432,Açai no centro,acai,"Encontrei produtos relevantes para ""Açai no centro"", com destaque para Mix, Sorvete napolitano 2L 😋 🤩 e Açaí.",682247058d086e34fd9743c9,Mix,6476517bf605a8001336802c,Sorvete napolitano 2L 😋 🤩,682247058d086e34fd974408,Açaí
14
+ 2026-03-16T19:52:29.592904,Acai na efapi,acai,"Encontrei produtos relevantes para ""Acai na efapi"", com destaque para Smothie 500ml, Smothie 500ml e Super Combo 3 Copos de Açaí Tradicional 1L😋.",64e1229bf82b840017dd9615,Smothie 500ml,5ed13bfe83e1e000122eacde,Smothie 500ml,64e1229bf82b840017dd9723,Super Combo 3 Copos de Açaí Tradicional 1L😋
15
+ 2026-03-16T19:53:07.388938,Pastel de pizza,pizza,"Encontrei produtos relevantes para ""Pastel de pizza"", com destaque para Strogonoff de Filé, Calabresa e Pizza Sachet de Muçarela.",609bbf087a23ed0012fe6494,Strogonoff de Filé,609ae76d2236c200139f1578,Calabresa,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela
16
+ 2026-03-16T19:59:19.645135,PAstel de Pizza,pizza,"Encontrei produtos relevantes para ""PAstel de Pizza"", com destaque para Pizza gigante com borda de catupiry, 3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo) e Rodízio De Pizza Com Espeto Corrido.",60b67d0b051001001221b236,Pizza gigante com borda de catupiry,608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),64eccc3477d0810014a22f15,Rodízio De Pizza Com Espeto Corrido
17
+ 2026-03-16T20:01:37.115103,Pastel de pizza,pizza,"Encontrei produtos relevantes para ""Pastel de pizza"", com destaque para Strogonoff de Filé, Calabresa e Pizza Sachet de Muçarela.",609bbf087a23ed0012fe6494,Strogonoff de Filé,609ae76d2236c200139f1578,Calabresa,64a9c54149ba5400137b7624,Pizza Sachet de Muçarela
18
+ 2026-03-16T20:02:38.055935,Coca cola na efapi,bebida,"Encontrei produtos relevantes para ""Coca cola na efapi"", com destaque para Coca-Cola 2L - Efapi, Coca-Cola Lata Sem açúcar 350ml e Coca-Cola Lata Sem açúcar 350ml.",5ec594f4fdc27700121776e9,Coca-Cola 2L - Efapi,630f608e66132d001210326b,Coca-Cola Lata Sem açúcar 350ml,650dfa282f768a00159882b3,Coca-Cola Lata Sem açúcar 350ml
19
+ 2026-03-16T20:03:23.938071,Açai com leite condensado,acai,"Encontrei produtos relevantes para ""Açai com leite condensado"", com destaque para 3 Copos de Açaí 180ml Com Leite Condensado + Ovomaltine + Amendoim + Confete, Açaí Ninho com Morango e Leite Condensado 700ml 🍓 e Açaí com Banana, Leite Ninho e Leite Condensado 500ml 🍌.",5fda6a303f12840012a3a257,3 Copos de Açaí 180ml Com Leite Condensado + Ovomaltine + Amendoim + Confete,668fee5dba1851c15b62469d,Açaí Ninho com Morango e Leite Condensado 700ml 🍓,668fecf2ba1851c15b61c21f," Açaí com Banana, Leite Ninho e Leite Condensado 500ml 🍌"
20
+ 2026-03-16T20:03:40.070185,acai com nutella,acai,"Encontrei produtos relevantes para ""acai com nutella"", com destaque para Geladinho Gourmet Ninho com Nutella, Açaí Ninho com Nutella 400ml e Açaí Ninho com Nutella 400ml.",69307a2462a1f6ab53263bb2,Geladinho Gourmet Ninho com Nutella,68a61a9073c453cae58f5c62,Açaí Ninho com Nutella 400ml,684341c9f7fa74dc21038717,Açaí Ninho com Nutella 400ml
21
+ 2026-03-16T20:04:20.340484,agua com gas,,"Encontrei produtos relevantes para ""agua com gas"", com destaque para ÁGUA COM GÁS, Água com Gás Premiun e Agua Mineral com Gás.",61f3f617379ac90012fed81b,ÁGUA COM GÁS,5ecc071505dbb90013d07989,Água com Gás Premiun,63922cd410245d001345721d,Agua Mineral com Gás
22
+ 2026-03-16T20:04:44.704358,pizza grande de calabressa,pizza,"Encontrei produtos relevantes para ""pizza grande de calabressa"", com destaque para Calabresa, Vegas e (TRÊS) Pizza Grande 35cm 12 Fatias (Portuguesa, Calabresa, Muçarela) + Coca-Cola 2 L 😎.",609ae76d2236c200139f1578,Calabresa,609bbcbc21f46f001307fbea,Vegas,622bced1c7f7c700124dd980,"(TRÊS) Pizza Grande 35cm 12 Fatias (Portuguesa, Calabresa, Muçarela) + Coca-Cola 2 L 😎"
23
+ 2026-03-16T20:04:59.141968,pizza media,pizza,"Encontrei produtos relevantes para ""pizza media"", com destaque para Pizza Média, Pizza Gigante + Pizza Média + Kuat 2l (Mais queijo) 🍕🍕 e Pizza Média + Pizza Broto Doce 🍕😍.",5dc9b94825bf860011280704,Pizza Média,608968d65e5c7c00124a0d34,Pizza Gigante + Pizza Média + Kuat 2l (Mais queijo) 🍕🍕,6053bbc261b68d00121b933f,Pizza Média + Pizza Broto Doce 🍕😍
24
+ 2026-03-16T20:05:23.749252,sorvete,,"Encontrei produtos relevantes para ""sorvete"", com destaque para Pote Sorvete Dielo Mania Abacaxi, Creme e Leite Condensado 2lt, Pote Sorvete Dielo Twister Blue Ice, Chocolate Branco e Kiwi 2lt e Sorvete Italiano 1,5L de Ninho Trufado com Nutella.",617f089ca1260b0013e3b8e2,"Pote Sorvete Dielo Mania Abacaxi, Creme e Leite Condensado 2lt",617f08c6534ea800125ea2ba,"Pote Sorvete Dielo Twister Blue Ice, Chocolate Branco e Kiwi 2lt",5bd741bac3533e000fb2b1c6,"Sorvete Italiano 1,5L de Ninho Trufado com Nutella"
25
+ 2026-03-16T20:09:13.727612,Pastel de Pizza,pizza,"Encontrei produtos relevantes para ""Pastel de Pizza"", com destaque para Calabresa, Pizza de Calabresa e Uma Pizza Dessas Para dividir com o Mozão.",609ae76d2236c200139f1578,Calabresa,60c5240bbd2ba700137a9876,Pizza de Calabresa,60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão
26
+ 2026-03-16T20:09:25.735089,Pastel de Pizza,pizza,"Encontrei produtos relevantes para ""Pastel de Pizza"", com destaque para Pizza de Calabresa, Uma Pizza Dessas Para dividir com o Mozão e Pizza Sachet de Mussarela.",60c5240bbd2ba700137a9876,Pizza de Calabresa,60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão,5ecbc130a0aac300135e1a8d,Pizza Sachet de Mussarela
27
+ 2026-03-16T20:09:34.254924,Pastel,pastel,"Encontrei produtos relevantes para ""Pastel"", com destaque para CARNE COM CATUPIRY, CARNE COM CATUPIRY e STROGONOFF.",60788ce43ce2d000142dd661,CARNE COM CATUPIRY,60552b2911e1280012737a60,CARNE COM CATUPIRY,60788ce43ce2d000142dd663,STROGONOFF
28
+ 2026-03-16T20:09:46.826103,Pastel de Pizza,pizza,"Encontrei produtos relevantes para ""Pastel de Pizza"", com destaque para Uma Pizza Dessas Para dividir com o Mozão, Pizza Sachet de Mussarela e Pizze CARNE DE PANELA.",60b4f786bce1db0012d1eabf,Uma Pizza Dessas Para dividir com o Mozão,5ecbc130a0aac300135e1a8d,Pizza Sachet de Mussarela,61ded064ff8457001419443d,Pizze CARNE DE PANELA
29
+ 2026-03-16T20:10:26.555890,Pastel de calabresa,pastel,"Encontrei produtos relevantes para ""Pastel de calabresa"", com destaque para Pastel frito de Calabresa com Queijo, STROGONOFF DE FRANGO e STROGONOFF DE FRANGO.",688bcf37691a3584055bbe4a,Pastel frito de Calabresa com Queijo,60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,60788ce43ce2d000142dd664,STROGONOFF DE FRANGO
30
+ 2026-03-16T20:10:40.768195,Pastel de frango,pastel,"Encontrei produtos relevantes para ""Pastel de frango"", com destaque para STROGONOFF DE FRANGO, STROGONOFF DE FRANGO e Pastel de Frango.",60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,60788ce43ce2d000142dd664,STROGONOFF DE FRANGO,5ed79e1eccc8d00013b8e3e2,Pastel de Frango
31
+ 2026-03-16T20:11:09.226271,Frango assado,,"Encontrei produtos relevantes para ""Frango assado"", com destaque para Frango Recheado com Bacon e Mandioca, Saltenha de frango com azeitona e Folhado de frango com catupiry.",6081c8b4725f9c0019afdb83,Frango Recheado com Bacon e Mandioca,5fade73c1e22660012841484,Saltenha de frango com azeitona,5fade73c1e22660012841489,Folhado de frango com catupiry
32
+ 2026-03-16T20:16:39.814455,Salgados,,"Encontrei produtos relevantes para ""Salgados"", com destaque para Assado Integral de Brócolis, Croissant Queijo e Croissant Frango.",67797f924a96dd7edc3b388f,Assado Integral de Brócolis,67797add4a96dd7edc3a9ef3,Croissant Queijo,67797ac34a96dd7edc3a9c4a,Croissant Frango
33
+ 2026-03-16T20:17:15.795801,cento de salgados,,"Encontrei produtos relevantes para ""cento de salgados"", com destaque para Assado Integral de Brócolis, Cento de Salgados Sortidos e Deliciosoos! 🤗: Risoles + Coxinha + Pastel Bolha + Bolinha de Queijo e Folhado de Frango.",67797f924a96dd7edc3b388f,Assado Integral de Brócolis,5f75c3af9a4a5c00122c8c1f,Cento de Salgados Sortidos e Deliciosoos! 🤗: Risoles + Coxinha + Pastel Bolha + Bolinha de Queijo,67797a0e4a96dd7edc3a87dd,Folhado de Frango
34
+ 2026-03-16T20:24:13.301442,Pastel de queijo,pastel,"Encontrei produtos relevantes para ""Pastel de queijo"", com destaque para STROGONOFF DE FRANGO, STROGONOFF DE FRANGO e Pastel de Pizza.",60552ebe68e56f0012387a62,STROGONOFF DE FRANGO,60788ce43ce2d000142dd664,STROGONOFF DE FRANGO,5f295637f76c8b001292d7bb,Pastel de Pizza
35
+ 2026-03-16T20:24:29.878294,PAstel de Pizza,pizza,"Encontrei produtos relevantes para ""PAstel de Pizza"", com destaque para 3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo), Rodízio De Pizza Com Espeto Corrido e Mini Pizza de Calabresa.",608968d75e5c7c00124a1201,3 Pizzas Gigantes + 1 Coca 2l🤤🍕 (Mais queijo),64eccc3477d0810014a22f15,Rodízio De Pizza Com Espeto Corrido,668593aa337d49d90e8117a9,Mini Pizza de Calabresa
36
+ 2026-03-16T20:26:33.135734,Pastel de pizza,pastel,"Encontrei produtos relevantes para ""Pastel de pizza"", com destaque para Pastel de Pizza, Pastel de Pizza e Pastel de Pizza.",5f295637f76c8b001292d7bb,Pastel de Pizza,5ed79e4bccc8d00013b8e474,Pastel de Pizza,688bcf36691a3584055bbcc3,Pastel de Pizza
37
+ 2026-03-21T19:44:08.374899,pizza,pizza,"Encontrei produtos relevantes para ""pizza"", com destaque para Pizza Média, Zucca Cabotiá (6 fatias) e Pizza Pequena.",5dc9b94825bf860011280704,Pizza Média,68aa46ec5829f3efe155504c,Zucca Cabotiá (6 fatias),63f4b1ab27f1ab0013ee1d2d,Pizza Pequena
38
+ 2026-03-21T19:45:33.684937,quero uma pizza grande de strogonoffe com refri incluso,pizza,"Encontrei produtos relevantes para ""quero uma pizza grande de strogonoffe com refri incluso"", com destaque para 🫨COMBÂO NOTA 1001!!😋 Pizza Grande + Borda Recheada + Refri, 😍🍕Mega Combo!!🍕😍 Pizza Grande + Borda + Refri 1,5L e 😍SUPER COMBO!!!🥰 3 Pizza Grande + Borda + Refri.",660b07f3b8c7bec046765906,🫨COMBÂO NOTA 1001!!😋 Pizza Grande + Borda Recheada + Refri,660ef142f19762dd1fbacb8d,"😍🍕Mega Combo!!🍕😍 Pizza Grande + Borda + Refri 1,5L",660b1322b8c7bec04679f273,😍SUPER COMBO!!!🥰 3 Pizza Grande + Borda + Refri
39
+ 2026-03-21T19:46:24.323868,eu não quero borda,,"Encontrei produtos relevantes para ""eu não quero borda"", com destaque para Caixinha ""Quero morar no seu abraço..."", Caixa Eu Te Amo Branca e Gretanese, inexplicavelmente Maionese....",62e1464ace20d10013d16d6f,"Caixinha ""Quero morar no seu abraço...""",62a53fcb248d720013699b5e,Caixa Eu Te Amo Branca,63d3c14522c67e0013224a34,"Gretanese, inexplicavelmente Maionese..."
40
+ 2026-03-21T19:47:25.479297,quero açai com cento de salgados e refri,acai,"Encontrei produtos relevantes para ""quero açai com cento de salgados e refri"", com destaque para 25 Croquetes de Calabresa com Queijo, 10 Unidades de Croquetes de Calabresa com Queijo e 40 Salgados mistos + Pepsi de 2 litros.",6890ea99434eb6137e3f7636,25 Croquetes de Calabresa com Queijo,6890ea9a434eb6137e3f79a2,10 Unidades de Croquetes de Calabresa com Queijo,6890ea99434eb6137e3f76df,40 Salgados mistos + Pepsi de 2 litros
41
+ 2026-03-21T19:48:28.093710,quero massa carbonara,,"Encontrei produtos relevantes para ""quero massa carbonara"", com destaque para Massa - Carbonara, Carbonara e Massa Carbonara 250g.",5ecbf6174eb0670012300212,Massa - Carbonara,67ed98adfe7b13f4711c10bc,Carbonara,5eb2ab989013a600127b0047,Massa Carbonara 250g
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-dotenv
4
+ pandas
5
+ numpy
6
+ torch
7
+ transformers
8
+ scikit-learn