Skip to content

Commit 57b481d

Browse files
committed
chore: initial project structure
0 parents  commit 57b481d

12 files changed

Lines changed: 133 additions & 0 deletions

File tree

.github/workflows/ci.yml

Whitespace-only changes.

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
venv/
2+
.env
3+
__pycache__/
4+
*.pyc
5+
*.pyo
6+
*.tmp
7+
uploads/
8+
.vscode/
9+
.idea/

Dockerfile

Whitespace-only changes.

backend/classifier.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
import json
3+
from groq import Groq
4+
from dotenv import load_dotenv
5+
6+
load_dotenv()
7+
8+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
9+
10+
SYSTEM_PROMPT = """
11+
Você é um assistente especializado em classificação de emails corporativos do setor financeiro.
12+
13+
Sua tarefa é analisar o conteúdo do email e retornar EXATAMENTE neste formato JSON:
14+
{
15+
"categoria": "Produtivo" ou "Improdutivo",
16+
"confianca": número de 0 a 100,
17+
"motivo": "explicação curta do motivo da classificação",
18+
"resposta_sugerida": "resposta automática adequada para enviar ao remetente"
19+
}
20+
21+
Definições:
22+
- Produtivo: emails que requerem ação ou resposta (suporte técnico, dúvidas, status de casos, solicitações)
23+
- Improdutivo: emails que não requerem ação imediata (felicitações, agradecimentos, mensagens sociais)
24+
25+
Retorne APENAS o JSON, sem texto adicional, sem markdown, sem explicações.
26+
"""
27+
28+
def classify_email(email_text: str) -> dict:
29+
response = client.chat.completions.create(
30+
model="llama-3.3-70b-versatile",
31+
messages=[
32+
{"role": "system", "content": SYSTEM_PROMPT},
33+
{"role": "user", "content": f"Email para classificar:\n{email_text}"}
34+
],
35+
temperature=0.1
36+
)
37+
38+
raw = response.choices[0].message.content.strip()
39+
40+
if raw.startswith("```"):
41+
raw = raw.split("```")[1]
42+
if raw.startswith("json"):
43+
raw = raw[4:]
44+
45+
result = json.loads(raw)
46+
return result

backend/main.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
2+
from fastapi.middleware.cors import CORSMiddleware
3+
from fastapi.staticfiles import StaticFiles
4+
from fastapi.responses import FileResponse
5+
from typing import Optional
6+
import os
7+
8+
from classifier import classify_email
9+
from utils import extract_text_from_file
10+
11+
app = FastAPI(title="Email Classifier API")
12+
13+
app.add_middleware(
14+
CORSMiddleware,
15+
allow_origins=["*"],
16+
allow_methods=["*"],
17+
allow_headers=["*"],
18+
)
19+
20+
app.mount("/static", StaticFiles(directory="../frontend"), name="static")
21+
22+
@app.get("/")
23+
def serve_frontend():
24+
return FileResponse("../frontend/index.html")
25+
26+
@app.get("/health")
27+
def health_check():
28+
return {"status": "ok"}
29+
30+
@app.post("/classify")
31+
async def classify(
32+
email_text: Optional[str] = Form(None),
33+
file: Optional[UploadFile] = File(None)
34+
):
35+
if not email_text and not file:
36+
raise HTTPException(
37+
status_code=400,
38+
detail="Envie um texto ou um arquivo."
39+
)
40+
41+
if file:
42+
contents = await file.read()
43+
try:
44+
email_text = extract_text_from_file(contents, file.filename)
45+
except ValueError as e:
46+
raise HTTPException(status_code=400, detail=str(e))
47+
48+
if not email_text.strip():
49+
raise HTTPException(
50+
status_code=400,
51+
detail="O conteúdo do email está vazio."
52+
)
53+
54+
try:
55+
result = classify_email(email_text)
56+
return result
57+
except Exception as e:
58+
raise HTTPException(
59+
status_code=500,
60+
detail=f"Erro ao classificar email: {str(e)}"
61+
)

backend/tests/__init__.py

Whitespace-only changes.

backend/tests/test_classifier.py

Whitespace-only changes.

backend/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import PyPDF2
2+
import io
3+
4+
def extract_text_from_file(file_bytes: bytes, filename: str) -> str:
5+
if filename.endswith(".pdf"):
6+
return extract_text_from_pdf(file_bytes)
7+
elif filename.endswith(".txt"):
8+
return file_bytes.decode("utf-8")
9+
else:
10+
raise ValueError("Formato não suportado. Use .txt ou .pdf")
11+
12+
def extract_text_from_pdf(file_bytes: bytes) -> str:
13+
reader = PyPDF2.PdfReader(io.BytesIO(file_bytes))
14+
text = ""
15+
for page in reader.pages:
16+
text += page.extract_text() or ""
17+
return text.strip()

docker-compose.yml

Whitespace-only changes.

frontend/index.html

Whitespace-only changes.

0 commit comments

Comments
 (0)