Musadiq7860 commited on
Commit
fe099da
·
0 Parent(s):

initial backend for huggingface

Browse files
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ GROQ_API_KEY=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
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
__pycache__/ai.cpython-313.pyc ADDED
Binary file (3.76 kB). View file
 
__pycache__/exporter.cpython-313.pyc ADDED
Binary file (1.55 kB). View file
 
__pycache__/main.cpython-313.pyc ADDED
Binary file (3.38 kB). View file
 
__pycache__/parser.cpython-313.pyc ADDED
Binary file (593 Bytes). View file
 
ai.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from groq import Groq
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env"))
6
+
7
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
8
+
9
+ def extract_jd_keywords(jd_text: str) -> str:
10
+ response = client.chat.completions.create(
11
+ model="llama-3.3-70b-versatile",
12
+ messages=[
13
+ {
14
+ "role": "system",
15
+ "content": "You are an ATS expert. Extract required skills, responsibilities, and seniority from job descriptions. Be concise and structured."
16
+ },
17
+ {
18
+ "role": "user",
19
+ "content": f"Extract key skills and requirements from this job description:\n\n{jd_text}"
20
+ }
21
+ ]
22
+ )
23
+ return response.choices[0].message.content
24
+
25
+
26
+ def rewrite_resume_bullets(resume_text: str, jd_keywords: str) -> str:
27
+ response = client.chat.completions.create(
28
+ model="llama-3.3-70b-versatile",
29
+ messages=[
30
+ {
31
+ "role": "system",
32
+ "content": """You are an expert resume writer. Rewrite the candidate's actual experience into strong resume bullets that naturally align with the job.
33
+
34
+ Rules:
35
+ - Only use skills and projects the candidate ACTUALLY has in their resume
36
+ - Mention their real projects by name where they are relevant
37
+ - Sound natural and human — not like a keyword list
38
+ - Start every bullet with a strong action verb
39
+ - Keep each bullet under 20 words
40
+ - Do NOT invent anything not in the resume
41
+ - Do NOT just copy job requirements as bullets"""
42
+ },
43
+ {
44
+ "role": "user",
45
+ "content": f"""Candidate resume:
46
+ {resume_text}
47
+
48
+ Job requirements:
49
+ {jd_keywords}
50
+
51
+ Rewrite the candidate's real experience as strong resume bullets that highlight relevant skills and mention actual projects by name."""
52
+ }
53
+ ]
54
+ )
55
+ return response.choices[0].message.content
56
+
57
+
58
+ def generate_cover_letter(resume_text: str, jd_text: str, tone: str) -> str:
59
+ response = client.chat.completions.create(
60
+ model="llama-3.3-70b-versatile",
61
+ messages=[
62
+ {
63
+ "role": "system",
64
+ "content": f"""You are an expert cover letter writer. Write in a {tone} tone.
65
+
66
+ Rules:
67
+ - Write exactly 3 paragraphs
68
+ - Reference the candidate's REAL projects from their resume by name
69
+ - Sound like a real human wrote this — confident and natural
70
+ - Never use cliches like 'I hope this finds you well' or 'I am writing to express my interest'
71
+ - Paragraph 1: who they are and why they are a strong fit for this specific role
72
+ - Paragraph 2: mention 2 specific real projects from their resume that match the job
73
+ - Paragraph 3: excitement about the role and a clear next step CTA"""
74
+ },
75
+ {
76
+ "role": "user",
77
+ "content": f"""Candidate resume:
78
+ {resume_text}
79
+
80
+ Job description:
81
+ {jd_text}
82
+
83
+ Write a natural, confident cover letter that references the candidate's real projects by name."""
84
+ }
85
+ ]
86
+ )
87
+ return response.choices[0].message.content
exporter.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from docx import Document
2
+ import io
3
+
4
+ def create_docx(bullets: str, cover_letter: str, candidate_name: str) -> bytes:
5
+ doc = Document()
6
+
7
+ doc.add_heading(f"{candidate_name} — Tailored Resume Bullets", level=1)
8
+ doc.add_paragraph("")
9
+
10
+ doc.add_heading("Rewritten Resume Bullets", level=2)
11
+ for line in bullets.split("\n"):
12
+ if line.strip():
13
+ doc.add_paragraph(line.strip(), style="List Bullet")
14
+
15
+ doc.add_paragraph("")
16
+ doc.add_heading("Cover Letter", level=2)
17
+ for para in cover_letter.split("\n"):
18
+ if para.strip():
19
+ doc.add_paragraph(para.strip())
20
+
21
+ buffer = io.BytesIO()
22
+ doc.save(buffer)
23
+ buffer.seek(0)
24
+ return buffer.getvalue()
main.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, Form
2
+ from fastapi.responses import Response
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from parser import extract_text_from_pdf
5
+ from ai import extract_jd_keywords, rewrite_resume_bullets, generate_cover_letter
6
+ from exporter import create_docx
7
+ from supabase import create_client
8
+ import os
9
+ from dotenv import load_dotenv
10
+
11
+ load_dotenv(dotenv_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
12
+
13
+ SUPABASE_URL = os.getenv("SUPABASE_URL")
14
+ SUPABASE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
15
+
16
+ if not SUPABASE_URL:
17
+ SUPABASE_URL = "https://wfoyeitrqcxsrlyihpnq.supabase.co"
18
+ if not SUPABASE_KEY:
19
+ SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Indmb3llaXRycWN4c3JseWlocG5xIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MzgyODU2NywiZXhwIjoyMDg5NDA0NTY3fQ.UB8_mzls0P558o1pCGE_u1RQ70cNoxUKjre2LIh6LZY"
20
+
21
+ app = FastAPI()
22
+
23
+ app.add_middleware(
24
+ CORSMiddleware,
25
+ allow_origins=["*"],
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ sb = create_client(SUPABASE_URL, SUPABASE_KEY)
31
+
32
+ @app.get("/")
33
+ def root():
34
+ return {"status": "Job Copilot API is running"}
35
+
36
+ @app.post("/tailor")
37
+ async def tailor(
38
+ jd_text: str = Form(...),
39
+ tone: str = Form(...),
40
+ candidate_name: str = Form(...),
41
+ user_id: str = Form(None),
42
+ resume_file: UploadFile = File(...)
43
+ ):
44
+ resume_bytes = await resume_file.read()
45
+ resume_text = extract_text_from_pdf(resume_bytes)
46
+
47
+ jd_keywords = extract_jd_keywords(jd_text)
48
+ bullets = rewrite_resume_bullets(resume_text, jd_keywords)
49
+ cover_letter = generate_cover_letter(resume_text, jd_text, tone)
50
+
51
+ if user_id:
52
+ try:
53
+ sb.table("applications").insert({
54
+ "user_id": user_id,
55
+ "candidate_name": candidate_name,
56
+ "job_description": jd_text[:500],
57
+ "tone": tone
58
+ }).execute()
59
+ except Exception as e:
60
+ print(f"Supabase insert error: {e}")
61
+
62
+ docx_bytes = create_docx(bullets, cover_letter, candidate_name)
63
+
64
+ return Response(
65
+ content=docx_bytes,
66
+ media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
67
+ headers={"Content-Disposition": f"attachment; filename=tailored_{candidate_name}.docx"}
68
+ )
parser.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import fitz
2
+
3
+ def extract_text_from_pdf(file_bytes: bytes) -> str:
4
+ doc = fitz.open(stream=file_bytes, filetype="pdf")
5
+ text = ""
6
+ for page in doc:
7
+ text += page.get_text()
8
+ return text.strip()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ pymupdf
5
+ python-docx
6
+ groq
7
+ python-dotenv
8
+ supabase