Spaces:
Runtime error
Runtime error
Upload 2 files
Browse files- __init__.py +0 -0
- main.py +214 -0
__init__.py
ADDED
|
File without changes
|
main.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
import databases
|
| 5 |
+
import sqlalchemy
|
| 6 |
+
from sqlalchemy.sql import select, and_
|
| 7 |
+
from fuzzywuzzy import process
|
| 8 |
+
import urllib.parse
|
| 9 |
+
import os
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
+
import json
|
| 12 |
+
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, DateTime, Boolean, Text, JSON
|
| 13 |
+
import time
|
| 14 |
+
from datetime import datetime
|
| 15 |
+
|
| 16 |
+
load_dotenv()
|
| 17 |
+
|
| 18 |
+
class JobQuery(BaseModel):
|
| 19 |
+
budget: str
|
| 20 |
+
experience: str
|
| 21 |
+
startDate: str
|
| 22 |
+
workType: str
|
| 23 |
+
semanticSearchString: str
|
| 24 |
+
|
| 25 |
+
username = urllib.parse.quote_plus('trial_user')
|
| 26 |
+
password = urllib.parse.quote_plus('trial_user_12345#')
|
| 27 |
+
host = '35.224.61.48'
|
| 28 |
+
port = '3306'
|
| 29 |
+
database_name = 'MERCOR_TRIAL_SCHEMA'
|
| 30 |
+
|
| 31 |
+
DATABASE_URL = f"mysql://{username}:{password}@{host}:{port}/{database_name}"
|
| 32 |
+
database = databases.Database(DATABASE_URL)
|
| 33 |
+
|
| 34 |
+
metadata = sqlalchemy.MetaData()
|
| 35 |
+
|
| 36 |
+
skills = sqlalchemy.Table(
|
| 37 |
+
"Skills",
|
| 38 |
+
metadata,
|
| 39 |
+
sqlalchemy.Column("skillId", sqlalchemy.String, primary_key=True),
|
| 40 |
+
sqlalchemy.Column("skillName", sqlalchemy.String),
|
| 41 |
+
sqlalchemy.Column("skillValue", sqlalchemy.String, unique=True),
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
jobs = sqlalchemy.Table(
|
| 45 |
+
"MercorUsers",
|
| 46 |
+
metadata,
|
| 47 |
+
sqlalchemy.Column("userId", sqlalchemy.String, primary_key=True),
|
| 48 |
+
sqlalchemy.Column("email", sqlalchemy.String, unique=True),
|
| 49 |
+
sqlalchemy.Column("name", sqlalchemy.String),
|
| 50 |
+
sqlalchemy.Column("phone", sqlalchemy.String),
|
| 51 |
+
sqlalchemy.Column("residence", JSON),
|
| 52 |
+
sqlalchemy.Column("profilePic", Text),
|
| 53 |
+
sqlalchemy.Column("createdAt", DateTime),
|
| 54 |
+
sqlalchemy.Column("lastLogin", DateTime),
|
| 55 |
+
sqlalchemy.Column("notes", Text),
|
| 56 |
+
sqlalchemy.Column("referralCode", sqlalchemy.String, unique=True),
|
| 57 |
+
sqlalchemy.Column("isGptEnabled", sqlalchemy.Boolean),
|
| 58 |
+
sqlalchemy.Column("preferredRole", sqlalchemy.String),
|
| 59 |
+
sqlalchemy.Column("fullTimeStatus", sqlalchemy.String),
|
| 60 |
+
sqlalchemy.Column("workAvailability", sqlalchemy.String),
|
| 61 |
+
sqlalchemy.Column("fullTimeSalaryCurrency", sqlalchemy.String),
|
| 62 |
+
sqlalchemy.Column("fullTimeSalary", sqlalchemy.String),
|
| 63 |
+
sqlalchemy.Column("partTimeSalaryCurrency", sqlalchemy.String),
|
| 64 |
+
sqlalchemy.Column("partTimeSalary", sqlalchemy.String),
|
| 65 |
+
sqlalchemy.Column("fullTime", sqlalchemy.Boolean),
|
| 66 |
+
sqlalchemy.Column("fullTimeAvailability", sqlalchemy.Integer),
|
| 67 |
+
sqlalchemy.Column("partTime", sqlalchemy.Boolean),
|
| 68 |
+
sqlalchemy.Column("partTimeAvailability", sqlalchemy.Integer),
|
| 69 |
+
sqlalchemy.Column("w8BenUrl", JSON),
|
| 70 |
+
sqlalchemy.Column("tosUrl", Text),
|
| 71 |
+
sqlalchemy.Column("policyUrls", JSON),
|
| 72 |
+
sqlalchemy.Column("isPreVetted", sqlalchemy.Boolean),
|
| 73 |
+
sqlalchemy.Column("isActive", sqlalchemy.Boolean),
|
| 74 |
+
sqlalchemy.Column("isComplete", sqlalchemy.Boolean),
|
| 75 |
+
sqlalchemy.Column("summary", Text),
|
| 76 |
+
sqlalchemy.Column("preVettedAt", DateTime)
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
MercorUserSkills = sqlalchemy.Table(
|
| 80 |
+
"MercorUserSkills",
|
| 81 |
+
metadata,
|
| 82 |
+
sqlalchemy.Column("userId", sqlalchemy.String, sqlalchemy.ForeignKey("MercorUsers.userId"), primary_key=True),
|
| 83 |
+
sqlalchemy.Column("skillId", sqlalchemy.String, sqlalchemy.ForeignKey("Skills.skillId"), primary_key=True),
|
| 84 |
+
sqlalchemy.Column("isPrimary", sqlalchemy.Boolean, default=False),
|
| 85 |
+
sqlalchemy.Column("order", Integer, default=0)
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
UserResume = sqlalchemy.Table(
|
| 89 |
+
"UserResume",
|
| 90 |
+
metadata,
|
| 91 |
+
sqlalchemy.Column("resumeId", sqlalchemy.String, primary_key=True),
|
| 92 |
+
sqlalchemy.Column("userId", sqlalchemy.String, sqlalchemy.ForeignKey("MercorUsers.userId")),
|
| 93 |
+
sqlalchemy.Column("url", Text),
|
| 94 |
+
sqlalchemy.Column("filename", String),
|
| 95 |
+
sqlalchemy.Column("createdAt", DateTime),
|
| 96 |
+
sqlalchemy.Column("updatedAt", DateTime),
|
| 97 |
+
sqlalchemy.Column("source", String),
|
| 98 |
+
sqlalchemy.Column("ocrText", Text),
|
| 99 |
+
sqlalchemy.Column("ocrEmail", String),
|
| 100 |
+
sqlalchemy.Column("ocrGithubUsername", String),
|
| 101 |
+
sqlalchemy.Column("resumeBasedQuestions", Text),
|
| 102 |
+
sqlalchemy.Column("isInvitedToInterview", Boolean),
|
| 103 |
+
sqlalchemy.Column("reminderTasksIds", JSON)
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
WorkExperience = sqlalchemy.Table(
|
| 107 |
+
"WorkExperience",
|
| 108 |
+
metadata,
|
| 109 |
+
sqlalchemy.Column("workExperienceId", sqlalchemy.String, primary_key=True),
|
| 110 |
+
sqlalchemy.Column("company", String),
|
| 111 |
+
sqlalchemy.Column("role", String),
|
| 112 |
+
sqlalchemy.Column("startDate", String),
|
| 113 |
+
sqlalchemy.Column("endDate", String),
|
| 114 |
+
sqlalchemy.Column("description", Text),
|
| 115 |
+
sqlalchemy.Column("locationCity", String),
|
| 116 |
+
sqlalchemy.Column("locationCountry", String),
|
| 117 |
+
sqlalchemy.Column("resumeId", String, sqlalchemy.ForeignKey("UserResume.resumeId"))
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
app = FastAPI()
|
| 121 |
+
# Configure CORS
|
| 122 |
+
origins = [
|
| 123 |
+
"http://localhost:3000", # Adjust this to the URL of your frontend
|
| 124 |
+
"http://127.0.0.1:3000"
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
app.add_middleware(
|
| 128 |
+
CORSMiddleware,
|
| 129 |
+
allow_origins=origins,
|
| 130 |
+
allow_credentials=True,
|
| 131 |
+
allow_methods=["*"],
|
| 132 |
+
allow_headers=["*"],
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
@app.on_event("startup")
|
| 136 |
+
async def startup():
|
| 137 |
+
await database.connect()
|
| 138 |
+
|
| 139 |
+
@app.on_event("shutdown")
|
| 140 |
+
async def shutdown():
|
| 141 |
+
await database.disconnect()
|
| 142 |
+
|
| 143 |
+
@app.post("/query-jobs/")
|
| 144 |
+
async def query_jobs(query: JobQuery):
|
| 145 |
+
start_time = time.time() # Start timing
|
| 146 |
+
|
| 147 |
+
# Fetch and match skills
|
| 148 |
+
all_skills = await database.fetch_all(select(skills.c.skillId, skills.c.skillName))
|
| 149 |
+
skills_dict = {skill['skillName']: skill['skillId'] for skill in all_skills}
|
| 150 |
+
matching_skills = process.extract(query.semanticSearchString, skills_dict.keys(), limit=10)
|
| 151 |
+
matching_skill_ids = [skills_dict[skill[0]] for skill in matching_skills if skill[1] > 70]
|
| 152 |
+
|
| 153 |
+
if not matching_skill_ids:
|
| 154 |
+
return {"message": "No skills matched your query.", "query": query.semanticSearchString}
|
| 155 |
+
|
| 156 |
+
user_skill_query = select(MercorUserSkills.c.userId).where(MercorUserSkills.c.skillId.in_(matching_skill_ids)).distinct()
|
| 157 |
+
user_ids = await database.fetch_all(user_skill_query)
|
| 158 |
+
user_ids = [user['userId'] for user in user_ids]
|
| 159 |
+
|
| 160 |
+
if not user_ids:
|
| 161 |
+
return {"message": "No users found with the matching skills."}
|
| 162 |
+
|
| 163 |
+
user_query = (
|
| 164 |
+
select(jobs)
|
| 165 |
+
.where(jobs.c.userId.in_(user_ids), jobs.c.fullTimeSalary <= int(query.budget))
|
| 166 |
+
.order_by(jobs.c.fullTimeSalary.desc()) # Sorting by fullTimeSalary descending
|
| 167 |
+
.limit(4) # Limiting to 4 results
|
| 168 |
+
)
|
| 169 |
+
users_with_skills = await database.fetch_all(user_query)
|
| 170 |
+
|
| 171 |
+
result = []
|
| 172 |
+
for user in users_with_skills:
|
| 173 |
+
user_skills_query = select(skills.c.skillName).join(MercorUserSkills, skills.c.skillId == MercorUserSkills.c.skillId).where(MercorUserSkills.c.userId == user['userId'])
|
| 174 |
+
user_skills = await database.fetch_all(user_skills_query)
|
| 175 |
+
user_skills_list = [skill['skillName'] for skill in user_skills]
|
| 176 |
+
|
| 177 |
+
# Fetch resume ID for user
|
| 178 |
+
resume_query = select(UserResume.c.resumeId).where(UserResume.c.userId == user['userId'])
|
| 179 |
+
resume_id = await database.fetch_one(resume_query)
|
| 180 |
+
|
| 181 |
+
# Fetch and process work experience
|
| 182 |
+
experience_query = select(WorkExperience).where(WorkExperience.c.resumeId == resume_id['resumeId'])
|
| 183 |
+
experiences = await database.fetch_all(experience_query)
|
| 184 |
+
companies = set()
|
| 185 |
+
total_experience = 0
|
| 186 |
+
|
| 187 |
+
for exp in experiences:
|
| 188 |
+
companies.add(exp['company'])
|
| 189 |
+
# Calculate duration
|
| 190 |
+
start_date = datetime.strptime(exp['startDate'], '%Y') if exp['startDate'] else None
|
| 191 |
+
end_date = datetime.strptime(exp['endDate'], '%Y') if exp['endDate'] else datetime.now()
|
| 192 |
+
if start_date:
|
| 193 |
+
duration_years = (end_date.year - start_date.year)
|
| 194 |
+
total_experience += duration_years
|
| 195 |
+
|
| 196 |
+
experiences_list = [{'company': exp['company'], 'role': exp['role'], 'startDate': exp['startDate'], 'endDate': exp['endDate']} for exp in experiences]
|
| 197 |
+
|
| 198 |
+
result.append({
|
| 199 |
+
"user_id": user['userId'],
|
| 200 |
+
"name": user['name'],
|
| 201 |
+
"email": user['email'],
|
| 202 |
+
"skills": user_skills_list,
|
| 203 |
+
"full_time_salary": user['fullTimeSalary'],
|
| 204 |
+
"companies": list(companies),
|
| 205 |
+
"total_experience_years": total_experience
|
| 206 |
+
})
|
| 207 |
+
|
| 208 |
+
execution_time = time.time() - start_time # Calculate execution time
|
| 209 |
+
|
| 210 |
+
return {"users": result, "execution_time": execution_time}
|
| 211 |
+
|
| 212 |
+
if __name__ == "__main__":
|
| 213 |
+
import uvicorn
|
| 214 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|