Spaces:
Build error
Build error
Merge branch 'main' into NhanHT
Browse files- app/modules/crud_question_test/models/crud_question_tests.py +17 -0
- app/modules/matching_cv/models/match_cv_jd_model.py +2 -2
- app/modules/matching_cv/models/matching_cv_logic.py +1 -1
- app/modules/question_tests_retrieval/__init__.py +5 -4
- app/modules/question_tests_retrieval/models/question_tests_logic.py +74 -33
- requirements.txt +8 -1
app/modules/crud_question_test/models/crud_question_tests.py
CHANGED
@@ -68,6 +68,8 @@ def create_question_test(data):
|
|
68 |
def update_question_test(id, data):
|
69 |
# Update a document by id
|
70 |
firebase_db.collection("question_tests").document(id).update(data)
|
|
|
|
|
71 |
return True
|
72 |
|
73 |
def delete_question_test(id):
|
@@ -76,4 +78,19 @@ def delete_question_test(id):
|
|
76 |
remove_file_question_tests(file_url)
|
77 |
# Delete a document by id
|
78 |
firebase_db.collection("question_tests").document(id).delete()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
return True
|
|
|
68 |
def update_question_test(id, data):
|
69 |
# Update a document by id
|
70 |
firebase_db.collection("question_tests").document(id).update(data)
|
71 |
+
# Update corrensponding vector in Qdrant
|
72 |
+
|
73 |
return True
|
74 |
|
75 |
def delete_question_test(id):
|
|
|
78 |
remove_file_question_tests(file_url)
|
79 |
# Delete a document by id
|
80 |
firebase_db.collection("question_tests").document(id).delete()
|
81 |
+
|
82 |
+
# Delete corresponding vector from Qdrant
|
83 |
+
qdrant_client.delete(
|
84 |
+
collection_name="question_tests",
|
85 |
+
points_selector=models.FilterSelector(
|
86 |
+
filter=models.Filter(
|
87 |
+
must=[
|
88 |
+
models.FieldCondition(
|
89 |
+
key="id",
|
90 |
+
match=models.MatchValue(value=id),
|
91 |
+
),
|
92 |
+
],
|
93 |
+
)
|
94 |
+
),
|
95 |
+
)
|
96 |
return True
|
app/modules/matching_cv/models/match_cv_jd_model.py
CHANGED
@@ -2,6 +2,6 @@ from fastapi import APIRouter, UploadFile, File
|
|
2 |
|
3 |
class Match_JD_CV_Model:
|
4 |
jd = UploadFile
|
5 |
-
jd_default = File(..., description="Upload JD file", media_type=["text/plain"])
|
6 |
cv = UploadFile
|
7 |
-
cv_default = File(..., description="Upload CV file", media_type=["application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"])
|
|
|
2 |
|
3 |
class Match_JD_CV_Model:
|
4 |
jd = UploadFile
|
5 |
+
jd_default = File(..., description="Upload JD file (only .txt file)", media_type=["text/plain"])
|
6 |
cv = UploadFile
|
7 |
+
cv_default = File(..., description="Upload CV file (only .pdf and .docx)", media_type=["application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"])
|
app/modules/matching_cv/models/matching_cv_logic.py
CHANGED
@@ -31,7 +31,7 @@ def result_matching_cv_jd(cv_text, jd_text):
|
|
31 |
Given the following CV and JD, calculate the percentage match between the candidate's qualifications and the job requirements:
|
32 |
CV: {cv}
|
33 |
JD: {jd}
|
34 |
-
To determine the match percentage, analyze the skills and experience in the CV and compare them to the requirements outlined in the JD. Provide the final match percentage as a numeric value between 0-100%, along with a brief explanation of your analysis. Follow this json format: {"Skills Match": {"Required Skills": "","Candidate Skills": "","Match Percentage": "",}, "Experience Match": {"Required Experience": "","Candidate Experience": "","Match Percentage": "",}, "Overall Match Percentage:": ""}
|
35 |
"""
|
36 |
)
|
37 |
),
|
|
|
31 |
Given the following CV and JD, calculate the percentage match between the candidate's qualifications and the job requirements:
|
32 |
CV: {cv}
|
33 |
JD: {jd}
|
34 |
+
To determine the match percentage, analyze the skills and experience in the CV and compare them to the requirements outlined in the JD. Provide the final match percentage as a numeric value between 0-100%, along with a brief explanation of your analysis. Follow this json format: {"Skills Match": {"Required Skills": "","Candidate Skills": "","Match Percentage": "",}, "Experience Match": {"Required Experience": "","Candidate Experience": "","Match Percentage": "",}, "Overall Match Percentage:": "", "Explanation": ""}
|
35 |
"""
|
36 |
)
|
37 |
),
|
app/modules/question_tests_retrieval/__init__.py
CHANGED
@@ -2,8 +2,8 @@ from fastapi import APIRouter, UploadFile, File
|
|
2 |
from typing import Annotated
|
3 |
|
4 |
from app.modules.question_tests_retrieval.models.jd2text import jobdes2text
|
5 |
-
# from app.modules.question_tests_retrieval.models.
|
6 |
-
from app.modules.question_tests_retrieval.models.question_tests_logic import
|
7 |
|
8 |
qtretrieval_router = APIRouter(prefix="/qtretrieval", tags=["qtretrieval"])
|
9 |
|
@@ -13,15 +13,16 @@ async def index():
|
|
13 |
|
14 |
@qtretrieval_router.post("/send_jd")
|
15 |
# only upload .txt file
|
16 |
-
async def send_jd(txt_file: Annotated[UploadFile, File(..., description="The JD file", media_type=["text/plain"])]):
|
17 |
try:
|
18 |
# read the txt file with format
|
19 |
jobdes = txt_file.file.read().decode("utf-8")
|
20 |
sumaryjd_text = jobdes2text(jobdes)
|
21 |
-
if
|
22 |
return {"message": "Send JD successfully and get question test successfully",
|
23 |
"sumaryjd_text": sumaryjd_text}
|
24 |
else:
|
25 |
return {"message": "Please upload only .txt file", "error": str(e)}
|
26 |
except Exception as e:
|
27 |
return {"message": "Please upload only .txt file", "error": str(e)}
|
|
|
|
2 |
from typing import Annotated
|
3 |
|
4 |
from app.modules.question_tests_retrieval.models.jd2text import jobdes2text
|
5 |
+
# from app.modules.question_tests_retrieval.models.text2vector import text2vector
|
6 |
+
from app.modules.question_tests_retrieval.models.question_tests_logic import get_question_tests
|
7 |
|
8 |
qtretrieval_router = APIRouter(prefix="/qtretrieval", tags=["qtretrieval"])
|
9 |
|
|
|
13 |
|
14 |
@qtretrieval_router.post("/send_jd")
|
15 |
# only upload .txt file
|
16 |
+
async def send_jd(txt_file: Annotated[UploadFile, File(..., description="The JD file (only .txt file)", media_type=["text/plain"])]):
|
17 |
try:
|
18 |
# read the txt file with format
|
19 |
jobdes = txt_file.file.read().decode("utf-8")
|
20 |
sumaryjd_text = jobdes2text(jobdes)
|
21 |
+
if get_question_tests(sumaryjd_text):
|
22 |
return {"message": "Send JD successfully and get question test successfully",
|
23 |
"sumaryjd_text": sumaryjd_text}
|
24 |
else:
|
25 |
return {"message": "Please upload only .txt file", "error": str(e)}
|
26 |
except Exception as e:
|
27 |
return {"message": "Please upload only .txt file", "error": str(e)}
|
28 |
+
|
app/modules/question_tests_retrieval/models/question_tests_logic.py
CHANGED
@@ -5,8 +5,10 @@ from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
|
5 |
from langchain.evaluation import load_evaluator
|
6 |
from langchain.evaluation import EmbeddingDistance
|
7 |
|
8 |
-
from app.modules.crud_question_test.models.crud_question_tests import
|
|
|
9 |
from app.configs.database import firebase_bucket
|
|
|
10 |
|
11 |
# Import API key
|
12 |
load_dotenv()
|
@@ -19,45 +21,84 @@ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
|
|
19 |
embedding_model = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=GOOGLE_API_KEY)
|
20 |
gemini_evaluator = load_evaluator("embedding_distance", distance_metric=EmbeddingDistance.COSINE, embeddings=embedding_model)
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
37 |
|
38 |
-
def download_question_test(
|
39 |
# check folder exist
|
40 |
if not os.path.exists('data/question_tests'):
|
41 |
os.makedirs('data/question_tests')
|
42 |
# download file from firebase storage using "gs://" link
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
46 |
return True
|
47 |
-
|
48 |
|
49 |
|
50 |
-
def get_question_test(text):
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
else:
|
59 |
-
|
60 |
-
if download_question_test(question_test_url):
|
61 |
-
return True
|
62 |
-
else:
|
63 |
-
return False
|
|
|
5 |
from langchain.evaluation import load_evaluator
|
6 |
from langchain.evaluation import EmbeddingDistance
|
7 |
|
8 |
+
from app.modules.crud_question_test.models.crud_question_tests import get_question_test_by_id
|
9 |
+
from app.modules.question_tests_retrieval.models.text2vector import text2vector
|
10 |
from app.configs.database import firebase_bucket
|
11 |
+
from app.configs.qdrant_db import qdrant_client
|
12 |
|
13 |
# Import API key
|
14 |
load_dotenv()
|
|
|
21 |
embedding_model = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=GOOGLE_API_KEY)
|
22 |
gemini_evaluator = load_evaluator("embedding_distance", distance_metric=EmbeddingDistance.COSINE, embeddings=embedding_model)
|
23 |
|
24 |
+
# def compare_vector(vector_extract, vector_des):
|
25 |
+
# maxnimun_value = 2
|
26 |
+
# for item in vector_des:
|
27 |
+
# two_object = (vector_extract, item)
|
28 |
+
# x = gemini_evaluator.evaluate_strings(prediction=two_object[0], reference=two_object[1])
|
29 |
+
# if x.get('score') < maxnimun_value:
|
30 |
+
# maxnimun_value = x.get('score')
|
31 |
+
# des_item_choose = item
|
32 |
+
# if maxnimun_value == 2:
|
33 |
+
# return False
|
34 |
+
# elif maxnimun_value < 0.3:
|
35 |
+
# return des_item_choose
|
36 |
+
# else:
|
37 |
+
# return False
|
38 |
+
|
39 |
+
def compare_vector(description_vector, max_number_of_points=3):
|
40 |
+
similarity_list = qdrant_client.search(
|
41 |
+
collection_name="question_tests",
|
42 |
+
query_vector=description_vector,
|
43 |
+
limit=max_number_of_points,
|
44 |
+
with_vectors=False,
|
45 |
+
with_payload=True,
|
46 |
+
)
|
47 |
|
48 |
+
formatted_similarity_list = []
|
49 |
+
for point in similarity_list:
|
50 |
+
formatted_similarity_list.append({"id": point.payload.get("id"), "score": point.score})
|
51 |
+
|
52 |
+
return formatted_similarity_list
|
53 |
+
|
54 |
+
# def download_question_test(question_test_url):
|
55 |
+
# # check folder exist
|
56 |
+
# if not os.path.exists('data/question_tests'):
|
57 |
+
# os.makedirs('data/question_tests')
|
58 |
+
# # download file from firebase storage using "gs://" link
|
59 |
+
# name_bucket = question_test_url.split(f"gs://{firebase_bucket.name}/")[1]
|
60 |
+
# blob = firebase_bucket.blob(name_bucket)
|
61 |
+
# blob.download_to_filename(f'data/question_tests/{name_bucket}')
|
62 |
+
# return True
|
63 |
|
64 |
+
def download_question_test(question_test_url_list):
|
65 |
# check folder exist
|
66 |
if not os.path.exists('data/question_tests'):
|
67 |
os.makedirs('data/question_tests')
|
68 |
# download file from firebase storage using "gs://" link
|
69 |
+
for url in question_test_url_list:
|
70 |
+
name_bucket = url.split(f"gs://{firebase_bucket.name}/")[1]
|
71 |
+
blob = firebase_bucket.blob(name_bucket)
|
72 |
+
blob.download_to_filename(f'data/question_tests/{name_bucket}')
|
73 |
+
|
74 |
return True
|
|
|
75 |
|
76 |
|
77 |
+
# def get_question_test(text):
|
78 |
+
# all_question_tests = get_all_question_tests()
|
79 |
+
# value_in_des = []
|
80 |
+
# for item in all_question_tests:
|
81 |
+
# value_in_des.append(item['question_tests_description'])
|
82 |
+
# des_item_choose = compare_vector(text, value_in_des)
|
83 |
+
# if des_item_choose == False:
|
84 |
+
# return "No question test found"
|
85 |
+
# else:
|
86 |
+
# question_test_url = get_question_test_url_by_description(des_item_choose)
|
87 |
+
# if download_question_test(question_test_url):
|
88 |
+
# return True
|
89 |
+
# else:
|
90 |
+
# return False
|
91 |
+
|
92 |
+
def get_question_tests(text):
|
93 |
+
# Get formatted similarity list
|
94 |
+
formatted_similarity_list = compare_vector(text2vector(text))
|
95 |
+
# Get corresponding document url in Firebase and download them
|
96 |
+
question_test_url_list = []
|
97 |
+
for point in formatted_similarity_list:
|
98 |
+
id = point.get("id")
|
99 |
+
question_test_url_list.append(get_question_test_by_id(id).get("question_tests_url"))
|
100 |
+
|
101 |
+
if download_question_test(question_test_url_list):
|
102 |
+
return True
|
103 |
else:
|
104 |
+
return False
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -30,13 +30,17 @@ google-generativeai==0.3.2
|
|
30 |
google-resumable-media==2.7.0
|
31 |
googleapis-common-protos==1.62.0
|
32 |
greenlet==3.0.3
|
33 |
-
grpcio==1.62.
|
34 |
grpcio-status==1.62.0
|
|
|
35 |
h11==0.14.0
|
|
|
|
|
36 |
httpcore==1.0.4
|
37 |
httplib2==0.22.0
|
38 |
httptools==0.6.1
|
39 |
httpx==0.27.0
|
|
|
40 |
idna==3.6
|
41 |
itsdangerous==2.1.2
|
42 |
Jinja2==3.1.3
|
@@ -57,6 +61,7 @@ mypy-extensions==1.0.0
|
|
57 |
numpy==1.26.4
|
58 |
orjson==3.9.15
|
59 |
packaging==23.2
|
|
|
60 |
proto-plus==1.23.0
|
61 |
protobuf==4.25.3
|
62 |
pyasn1==0.5.1
|
@@ -71,7 +76,9 @@ pyparsing==3.1.2
|
|
71 |
python-docx==1.1.0
|
72 |
python-dotenv==1.0.1
|
73 |
python-multipart==0.0.9
|
|
|
74 |
PyYAML==6.0.1
|
|
|
75 |
requests==2.31.0
|
76 |
rsa==4.9
|
77 |
sniffio==1.3.1
|
|
|
30 |
google-resumable-media==2.7.0
|
31 |
googleapis-common-protos==1.62.0
|
32 |
greenlet==3.0.3
|
33 |
+
grpcio==1.62.1
|
34 |
grpcio-status==1.62.0
|
35 |
+
grpcio-tools==1.62.1
|
36 |
h11==0.14.0
|
37 |
+
h2==4.1.0
|
38 |
+
hpack==4.0.0
|
39 |
httpcore==1.0.4
|
40 |
httplib2==0.22.0
|
41 |
httptools==0.6.1
|
42 |
httpx==0.27.0
|
43 |
+
hyperframe==6.0.1
|
44 |
idna==3.6
|
45 |
itsdangerous==2.1.2
|
46 |
Jinja2==3.1.3
|
|
|
61 |
numpy==1.26.4
|
62 |
orjson==3.9.15
|
63 |
packaging==23.2
|
64 |
+
portalocker==2.8.2
|
65 |
proto-plus==1.23.0
|
66 |
protobuf==4.25.3
|
67 |
pyasn1==0.5.1
|
|
|
76 |
python-docx==1.1.0
|
77 |
python-dotenv==1.0.1
|
78 |
python-multipart==0.0.9
|
79 |
+
pywin32==306
|
80 |
PyYAML==6.0.1
|
81 |
+
qdrant-client==1.8.0
|
82 |
requests==2.31.0
|
83 |
rsa==4.9
|
84 |
sniffio==1.3.1
|