Vincent Claes
commited on
Commit
•
a1372cb
1
Parent(s):
f54a323
jobfixers create HTML table
Browse files- app.py +100 -69
- recruiting_assistant.py +0 -2
- scripts/README.md +5 -0
- scripts/scrape_website.py +179 -0
- skills.py +97 -0
- utils.py +31 -0
app.py
CHANGED
@@ -2,81 +2,119 @@ import json
|
|
2 |
import os
|
3 |
import gradio as gr
|
4 |
import requests
|
|
|
|
|
5 |
import recruiting_assistant # Assuming this module provides the required reversed functionality
|
|
|
|
|
|
|
|
|
6 |
|
7 |
|
8 |
-
def
|
9 |
url = f"https://3jxjznzonb.execute-api.eu-west-1.amazonaws.com/dev/prediction" # vervang met uw API-eindpunt
|
10 |
headers = {
|
11 |
"Content-Type": "application/json",
|
12 |
"x-api-key": os.environ["API_KEY"],
|
13 |
} # pas headers indien nodig aan
|
14 |
-
response = requests.post(
|
15 |
-
url, headers=headers, data=json.dumps({"text": input_text})
|
16 |
-
)
|
17 |
response_data = response.json()
|
18 |
|
19 |
if "prediction" in response_data:
|
20 |
prediction = response_data["prediction"]
|
21 |
if isinstance(prediction, list):
|
22 |
-
#
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
Nationaliteit: Nederlandse
|
40 |
-
|
41 |
-
Profiel:
|
42 |
-
Een toegewijde en bekwame koeltechnieker met 8 jaar ervaring in het ontwerpen, installeren, onderhouden en repareren van koelsystemen. Technisch onderlegd en bekend met verschillende koeltechnieken. Een probleemoplosser die snel storingen kan diagnosticeren en efficiënte oplossingen kan implementeren. Goed in teamverband en klantgericht.
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
Service Technicus bij KoelTech B.V. - januari 2018 tot heden
|
47 |
-
|
48 |
-
Verantwoordelijk voor het installeren en onderhouden van commerciële en industriële koelsystemen.
|
49 |
-
Diagnose stellen en repareren van storingen in koelapparatuur.
|
50 |
-
Uitvoeren van preventief onderhoud om de prestaties van koelinstallaties te optimaliseren.
|
51 |
-
Klantgerichte aanpak om vragen en problemen van klanten op te lossen.
|
52 |
-
Assistent Koeltechnieker bij CoolAir Installaties - maart 2014 tot december 2017
|
53 |
-
|
54 |
-
Betrokken bij het ontwerp en de installatie van nieuwe koelsystemen in residentiële gebouwen.
|
55 |
-
Uitvoeren van druk- en temperatuurmetingen om de juiste werking van de systemen te waarborgen.
|
56 |
-
Oplossen van technische problemen en het vervangen van defecte onderdelen.
|
57 |
-
Het trainen van klanten in het juiste gebruik en onderhoud van koelapparatuur.
|
58 |
-
Opleiding:
|
59 |
-
|
60 |
-
MBO Koeltechniek - Technische School Stadsveld - 2014
|
61 |
-
Certificaat Veilig werken met koelinstallaties - Koelacademie Nederland - 2014
|
62 |
-
Vaardigheden:
|
63 |
|
64 |
-
Uitgebreide kennis van verschillende koeltechnieken en -systemen.
|
65 |
-
Sterk technisch inzicht en probleemoplossend vermogen.
|
66 |
-
Bekend met veiligheidsvoorschriften en -procedures in de koeltechniek.
|
67 |
-
Goede communicatieve vaardigheden om effectief te kunnen samenwerken met collega's en klanten.
|
68 |
-
Bekwaamheid in het lezen van technische tekeningen en schema's.
|
69 |
-
Talen:
|
70 |
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
Beschikbaar op verzoek.
|
75 |
|
76 |
-
|
77 |
-
""
|
78 |
-
|
|
|
|
|
|
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
demo = gr.Blocks()
|
82 |
|
@@ -84,12 +122,11 @@ with demo:
|
|
84 |
with gr.Group():
|
85 |
with gr.Box():
|
86 |
with gr.Row(elem_id="prompt-container").style(
|
87 |
-
|
88 |
):
|
89 |
with gr.Column():
|
90 |
gr.Markdown(
|
91 |
"""
|
92 |
-
|
93 |
## 1. Voer een CV in en krijg relevante vacatures terug.
|
94 |
"""
|
95 |
)
|
@@ -102,15 +139,12 @@ with demo:
|
|
102 |
rounded=(False, True, True, False),
|
103 |
full_width=False,
|
104 |
)
|
105 |
-
|
106 |
label="Top vacatures gevonden in de database",
|
107 |
)
|
108 |
-
b1.click(
|
109 |
-
zoek_vacature, inputs=text_cv, outputs=text_search_result
|
110 |
-
)
|
111 |
gr.Markdown(
|
112 |
"""
|
113 |
-
|
114 |
## 2. Selecteer een geschikte vacature voor deze CV, plak deze in het tekstveld en krijg een relevante intro.
|
115 |
"""
|
116 |
)
|
@@ -124,7 +158,6 @@ with demo:
|
|
124 |
)
|
125 |
gr.Markdown(
|
126 |
"""
|
127 |
-
|
128 |
## 3. U heeft een relevante intro.
|
129 |
"""
|
130 |
)
|
@@ -138,12 +171,10 @@ with demo:
|
|
138 |
|
139 |
gr.Examples(
|
140 |
examples=examples,
|
141 |
-
fn=
|
142 |
inputs=text_cv,
|
143 |
-
outputs=
|
144 |
cache_examples=False,
|
145 |
)
|
146 |
|
147 |
-
|
148 |
-
|
149 |
demo.launch()
|
|
|
2 |
import os
|
3 |
import gradio as gr
|
4 |
import requests
|
5 |
+
from langchain.chat_models import ChatOpenAI
|
6 |
+
|
7 |
import recruiting_assistant # Assuming this module provides the required reversed functionality
|
8 |
+
import skills
|
9 |
+
import utils
|
10 |
+
|
11 |
+
llm = ChatOpenAI(temperature=0.0, openai_api_key=os.environ["OPENAI"])
|
12 |
|
13 |
|
14 |
+
def search(resume):
|
15 |
url = f"https://3jxjznzonb.execute-api.eu-west-1.amazonaws.com/dev/prediction" # vervang met uw API-eindpunt
|
16 |
headers = {
|
17 |
"Content-Type": "application/json",
|
18 |
"x-api-key": os.environ["API_KEY"],
|
19 |
} # pas headers indien nodig aan
|
20 |
+
response = requests.post(url, headers=headers, data=json.dumps({"text": resume, "limit":3}))
|
|
|
|
|
21 |
response_data = response.json()
|
22 |
|
23 |
if "prediction" in response_data:
|
24 |
prediction = response_data["prediction"]
|
25 |
if isinstance(prediction, list):
|
26 |
+
# Convert prediction to HTML table
|
27 |
+
html_table = "<table>"
|
28 |
+
|
29 |
+
# Add table headers
|
30 |
+
html_table += "<tr><th>Vacancy</th><th>Score</th><th>Skills Match</th><th>Skills Missing</th></tr>"
|
31 |
+
|
32 |
+
for i, vacancy in enumerate(prediction):
|
33 |
+
(
|
34 |
+
score,
|
35 |
+
skills_intersection,
|
36 |
+
skills_not_in_intersection,
|
37 |
+
) = get_skills_score(vacancy=vacancy, resume=resume)
|
38 |
+
# Voeg een nieuwe regel toe na elke '.' en voeg "Vacature <volgnummer>:\n" toe voor elk item
|
39 |
+
updated_item = f"VACATURE {i + 1}: {vacancy}"
|
40 |
+
html_table += f"<tr><td>{updated_item}</td><td>{score}</td><td>{skills_intersection}</td><td>{skills_not_in_intersection}</td></tr>"
|
41 |
+
html_table += "</table>"
|
42 |
+
return html_table
|
|
|
|
|
|
|
|
|
43 |
|
44 |
+
return "niets teruggevonden ..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
+
def get_skills_score(vacancy, resume):
|
48 |
+
chain = skills.get_skills_chain(llm=llm)
|
49 |
+
result = chain({"vacancy": vacancy, "resume": resume})
|
|
|
50 |
|
51 |
+
vacancy_skills_predicted = utils.get_json_list_from_result(
|
52 |
+
result, "vacancy_skills_predicted"
|
53 |
+
)
|
54 |
+
resume_skills_predicted = utils.get_json_list_from_result(
|
55 |
+
result, "resume_skills_predicted"
|
56 |
+
)
|
57 |
|
58 |
+
skills_intersection = utils.get_intersection(
|
59 |
+
vacancy_skills_predicted, resume_skills_predicted
|
60 |
+
)
|
61 |
+
skills_not_in_intersection = utils.not_in_intersection(
|
62 |
+
vacancy_skills_predicted, resume_skills_predicted
|
63 |
+
)
|
64 |
+
match_score = utils.match(
|
65 |
+
present_features=skills_intersection,
|
66 |
+
not_present_features=skills_not_in_intersection,
|
67 |
+
)
|
68 |
+
return match_score, skills_intersection, skills_not_in_intersection
|
69 |
+
|
70 |
+
|
71 |
+
examples = [
|
72 |
+
"""
|
73 |
+
Naam: John Doe
|
74 |
+
Adres: Parkstraat 123, 1000 Stadsveld
|
75 |
+
Telefoon: +31 6 1234 5678
|
76 |
+
E-mail: [email protected]
|
77 |
+
Geboortedatum: 15 juli 1985
|
78 |
+
Nationaliteit: Nederlandse
|
79 |
+
|
80 |
+
Profiel:
|
81 |
+
Een toegewijde en bekwame koeltechnieker met 8 jaar ervaring in het ontwerpen, installeren, onderhouden en repareren van koelsystemen. Technisch onderlegd en bekend met verschillende koeltechnieken. Een probleemoplosser die snel storingen kan diagnosticeren en efficiënte oplossingen kan implementeren. Goed in teamverband en klantgericht.
|
82 |
+
|
83 |
+
Werkervaring:
|
84 |
+
|
85 |
+
Service Technicus bij KoelTech B.V. - januari 2018 tot heden
|
86 |
+
|
87 |
+
Verantwoordelijk voor het installeren en onderhouden van commerciële en industriële koelsystemen.
|
88 |
+
Diagnose stellen en repareren van storingen in koelapparatuur.
|
89 |
+
Uitvoeren van preventief onderhoud om de prestaties van koelinstallaties te optimaliseren.
|
90 |
+
Klantgerichte aanpak om vragen en problemen van klanten op te lossen.
|
91 |
+
Assistent Koeltechnieker bij CoolAir Installaties - maart 2014 tot december 2017
|
92 |
+
|
93 |
+
Betrokken bij het ontwerp en de installatie van nieuwe koelsystemen in residentiële gebouwen.
|
94 |
+
Uitvoeren van druk- en temperatuurmetingen om de juiste werking van de systemen te waarborgen.
|
95 |
+
Oplossen van technische problemen en het vervangen van defecte onderdelen.
|
96 |
+
Het trainen van klanten in het juiste gebruik en onderhoud van koelapparatuur.
|
97 |
+
Opleiding:
|
98 |
+
|
99 |
+
MBO Koeltechniek - Technische School Stadsveld - 2014
|
100 |
+
Certificaat Veilig werken met koelinstallaties - Koelacademie Nederland - 2014
|
101 |
+
Vaardigheden:
|
102 |
+
|
103 |
+
Uitgebreide kennis van verschillende koeltechnieken en -systemen.
|
104 |
+
Sterk technisch inzicht en probleemoplossend vermogen.
|
105 |
+
Bekend met veiligheidsvoorschriften en -procedures in de koeltechniek.
|
106 |
+
Goede communicatieve vaardigheden om effectief te kunnen samenwerken met collega's en klanten.
|
107 |
+
Bekwaamheid in het lezen van technische tekeningen en schema's.
|
108 |
+
Talen:
|
109 |
+
|
110 |
+
Nederlands: Moedertaal
|
111 |
+
Engels: Goed
|
112 |
+
Referenties:
|
113 |
+
Beschikbaar op verzoek.
|
114 |
+
|
115 |
+
Opmerking: Dit CV bevat fictieve gegevens en dient alleen ter illustratie. Gebruik het als een sjabloon en vervang de gegevens door je eigen informatie bij het maken van een CV.
|
116 |
+
"""
|
117 |
+
]
|
118 |
|
119 |
demo = gr.Blocks()
|
120 |
|
|
|
122 |
with gr.Group():
|
123 |
with gr.Box():
|
124 |
with gr.Row(elem_id="prompt-container").style(
|
125 |
+
mobile_collapse=False, equal_height=True
|
126 |
):
|
127 |
with gr.Column():
|
128 |
gr.Markdown(
|
129 |
"""
|
|
|
130 |
## 1. Voer een CV in en krijg relevante vacatures terug.
|
131 |
"""
|
132 |
)
|
|
|
139 |
rounded=(False, True, True, False),
|
140 |
full_width=False,
|
141 |
)
|
142 |
+
html_search_result = gr.HTML(
|
143 |
label="Top vacatures gevonden in de database",
|
144 |
)
|
145 |
+
b1.click(search, inputs=text_cv, outputs=html_search_result)
|
|
|
|
|
146 |
gr.Markdown(
|
147 |
"""
|
|
|
148 |
## 2. Selecteer een geschikte vacature voor deze CV, plak deze in het tekstveld en krijg een relevante intro.
|
149 |
"""
|
150 |
)
|
|
|
158 |
)
|
159 |
gr.Markdown(
|
160 |
"""
|
|
|
161 |
## 3. U heeft een relevante intro.
|
162 |
"""
|
163 |
)
|
|
|
171 |
|
172 |
gr.Examples(
|
173 |
examples=examples,
|
174 |
+
fn=search,
|
175 |
inputs=text_cv,
|
176 |
+
outputs=html_search_result,
|
177 |
cache_examples=False,
|
178 |
)
|
179 |
|
|
|
|
|
180 |
demo.launch()
|
recruiting_assistant.py
CHANGED
@@ -166,5 +166,3 @@ def create_intro(vacancy, resume):
|
|
166 |
Not Relevant Skills: {",".join(resume_skills["skills_not_present"])}
|
167 |
"""
|
168 |
return result["introduction_email"], score
|
169 |
-
|
170 |
-
|
|
|
166 |
Not Relevant Skills: {",".join(resume_skills["skills_not_present"])}
|
167 |
"""
|
168 |
return result["introduction_email"], score
|
|
|
|
scripts/README.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# JobFixers
|
2 |
+
|
3 |
+
## Code
|
4 |
+
|
5 |
+
Generated from: https://chat.openai.com/share/3bc1e603-e14a-4d9a-b51b-1231e488a95f
|
scripts/scrape_website.py
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import openai
|
3 |
+
import pandas as pd
|
4 |
+
import json
|
5 |
+
from selenium import webdriver
|
6 |
+
from selenium.webdriver.chrome.service import Service
|
7 |
+
from webdriver_manager.chrome import ChromeDriverManager
|
8 |
+
from selenium.webdriver.common.by import By
|
9 |
+
from selenium.common.exceptions import (
|
10 |
+
NoSuchElementException,
|
11 |
+
TimeoutException,
|
12 |
+
WebDriverException,
|
13 |
+
)
|
14 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
15 |
+
from selenium.webdriver.support import expected_conditions as EC
|
16 |
+
from bs4 import BeautifulSoup
|
17 |
+
from urllib.parse import urlparse
|
18 |
+
import time
|
19 |
+
|
20 |
+
# Set up OpenAI API
|
21 |
+
openai.api_key = os.getenv("OPENAI")
|
22 |
+
|
23 |
+
# Initialize followup number
|
24 |
+
followup_number = 0
|
25 |
+
|
26 |
+
# Start with page 1
|
27 |
+
page_number = 4
|
28 |
+
|
29 |
+
# Get the current working directory
|
30 |
+
cwd = os.getcwd()
|
31 |
+
|
32 |
+
# Path to the CSV file
|
33 |
+
csv_file = os.path.join(cwd, "vacancies.csv")
|
34 |
+
|
35 |
+
while True:
|
36 |
+
try:
|
37 |
+
# Setup webdriver
|
38 |
+
s = Service(ChromeDriverManager().install())
|
39 |
+
driver = webdriver.Chrome(service=s)
|
40 |
+
|
41 |
+
# The URL of the page with the buttons
|
42 |
+
url = f"https://vacatures.jobfixers.be/zoek?page={page_number}&size=50&sortBy=updated:desc&initial=true"
|
43 |
+
|
44 |
+
# Navigate to the page
|
45 |
+
driver.get(url)
|
46 |
+
|
47 |
+
# Wait for the page to load
|
48 |
+
time.sleep(5)
|
49 |
+
|
50 |
+
# Find the buttons
|
51 |
+
buttons = WebDriverWait(driver, 10).until(
|
52 |
+
EC.presence_of_all_elements_located(
|
53 |
+
(
|
54 |
+
By.XPATH,
|
55 |
+
'//button[@mat-button and contains(@class, "mat-focus-indicator mat-button mat-button-base")]',
|
56 |
+
)
|
57 |
+
)
|
58 |
+
)
|
59 |
+
|
60 |
+
for i in range(len(buttons)):
|
61 |
+
# Find the buttons again to avoid StaleElementReferenceException
|
62 |
+
buttons = WebDriverWait(driver, 10).until(
|
63 |
+
EC.presence_of_all_elements_located(
|
64 |
+
(
|
65 |
+
By.XPATH,
|
66 |
+
'//button[@mat-button and contains(@class, "mat-focus-indicator mat-button mat-button-base")]',
|
67 |
+
)
|
68 |
+
)
|
69 |
+
)
|
70 |
+
|
71 |
+
# Click the button
|
72 |
+
buttons[i].click()
|
73 |
+
|
74 |
+
# Wait for the new page to load
|
75 |
+
time.sleep(5)
|
76 |
+
|
77 |
+
# Get the page source
|
78 |
+
html = driver.page_source
|
79 |
+
|
80 |
+
# Parse the HTML with BeautifulSoup
|
81 |
+
soup = BeautifulSoup(html, "html.parser")
|
82 |
+
|
83 |
+
# Extract relevant items related to the vacancy
|
84 |
+
vacancy_detail = {}
|
85 |
+
|
86 |
+
# Extracting the job position
|
87 |
+
vacancy_detail["Position"] = soup.select_one(
|
88 |
+
".vacancy-detail__content__header__position"
|
89 |
+
).text.strip()
|
90 |
+
|
91 |
+
# Extracting the location
|
92 |
+
vacancy_detail["Location"] = soup.select_one(
|
93 |
+
".vacancy-detail__content__header__location a"
|
94 |
+
).text.strip()
|
95 |
+
|
96 |
+
# Extracting the description
|
97 |
+
description = soup.select_one(
|
98 |
+
".vacancy-detail__content__body__description__details"
|
99 |
+
).get_text(separator=" ")
|
100 |
+
vacancy_detail["Description"] = description.strip()
|
101 |
+
|
102 |
+
# Extracting the profile details
|
103 |
+
profile_details = soup.select(
|
104 |
+
".vacancy-detail__content__body__profile__details li"
|
105 |
+
)
|
106 |
+
vacancy_detail["Profile"] = [
|
107 |
+
detail.text.strip() for detail in profile_details
|
108 |
+
]
|
109 |
+
|
110 |
+
# Extracting the list of competences
|
111 |
+
competences = soup.select(
|
112 |
+
".vacancy-detail__content__body__competences__details li"
|
113 |
+
)
|
114 |
+
vacancy_detail["Competences"] = [
|
115 |
+
competence.text.strip() for competence in competences
|
116 |
+
]
|
117 |
+
|
118 |
+
# Extracting the offer details
|
119 |
+
offer_details = soup.select(
|
120 |
+
".vacancy-detail__content__body__offer__details li"
|
121 |
+
)
|
122 |
+
vacancy_detail["Offer"] = [offer.text.strip() for offer in offer_details]
|
123 |
+
|
124 |
+
# Add the webpage and followup number
|
125 |
+
vacancy_detail["Webpage"] = driver.current_url
|
126 |
+
vacancy_detail["Followup_Number"] = followup_number
|
127 |
+
|
128 |
+
# Get the final part of the URL
|
129 |
+
parsed_url = urlparse(driver.current_url)
|
130 |
+
vacancy_detail["Vacancy_Id"] = os.path.basename(parsed_url.path)
|
131 |
+
|
132 |
+
# Add the full URL of the webpage
|
133 |
+
vacancy_detail["Full_URL"] = driver.current_url
|
134 |
+
|
135 |
+
# Concatenate all the vacancy details into a single string
|
136 |
+
vacancy_detail[
|
137 |
+
"Vacancy"
|
138 |
+
] = f"Position: {vacancy_detail['Position']}\nLocation: {vacancy_detail['Location']}\nDescription: {vacancy_detail['Description']}\nProfile: {' '.join(vacancy_detail['Profile'])}\nCompetences: {' '.join(vacancy_detail['Competences'])}\nOffer: {' '.join(vacancy_detail['Offer'])}"
|
139 |
+
|
140 |
+
# Add the entire dictionary as a JSON string to a new key "Vacancy_JSON"
|
141 |
+
vacancy_detail["Vacancy_JSON"] = json.dumps(vacancy_detail)
|
142 |
+
|
143 |
+
# Increment the followup number
|
144 |
+
followup_number += 1
|
145 |
+
|
146 |
+
# Print the vacancy detail, page number and follow-up number
|
147 |
+
print(f"Page Number: {page_number}, Follow-up Number: {followup_number}")
|
148 |
+
print(vacancy_detail)
|
149 |
+
|
150 |
+
# Append the vacancy detail to the CSV file
|
151 |
+
df = pd.DataFrame([vacancy_detail])
|
152 |
+
df.to_csv(
|
153 |
+
csv_file, mode="a", header=not os.path.exists(csv_file), index=False
|
154 |
+
)
|
155 |
+
|
156 |
+
# Go back to the list page
|
157 |
+
driver.back()
|
158 |
+
time.sleep(5)
|
159 |
+
|
160 |
+
# Go to the next page
|
161 |
+
page_number += 1
|
162 |
+
|
163 |
+
except WebDriverException as e:
|
164 |
+
print(
|
165 |
+
f"WebDriverException occurred: {e}. Restarting the browser and waiting for 1 minute before trying the next page."
|
166 |
+
)
|
167 |
+
driver.quit()
|
168 |
+
time.sleep(60)
|
169 |
+
page_number += 1
|
170 |
+
|
171 |
+
except Exception as e:
|
172 |
+
print(
|
173 |
+
f"Exception occurred: {e}. Waiting for 1 minute before trying the next page."
|
174 |
+
)
|
175 |
+
time.sleep(60)
|
176 |
+
page_number += 1
|
177 |
+
|
178 |
+
# Close the driver
|
179 |
+
driver.quit()
|
skills.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain import LLMChain
|
2 |
+
from langchain.chains import SequentialChain
|
3 |
+
from langchain.prompts import ChatPromptTemplate
|
4 |
+
|
5 |
+
|
6 |
+
def get_vacancy_skills_chain(llm) -> LLMChain:
|
7 |
+
template_vacancy_get_skills = """
|
8 |
+
Given the following vacancy delimited by three backticks, retrieve the skills that are requested.
|
9 |
+
Return the skills as a JSON list on 1 line, do not add newlines or any other text.
|
10 |
+
|
11 |
+
```
|
12 |
+
{vacancy}
|
13 |
+
```
|
14 |
+
"""
|
15 |
+
|
16 |
+
prompt_vacancy_skills = ChatPromptTemplate.from_template(
|
17 |
+
template=template_vacancy_get_skills
|
18 |
+
)
|
19 |
+
vacancy_skills = LLMChain(
|
20 |
+
llm=llm, prompt=prompt_vacancy_skills, output_key="vacancy_skills_predicted"
|
21 |
+
)
|
22 |
+
return vacancy_skills
|
23 |
+
|
24 |
+
|
25 |
+
def get_resume_skills_chain(llm) -> LLMChain:
|
26 |
+
template_resume_skills = """
|
27 |
+
Given the following resume delimited by three backticks, retrieve the skills this data scientist possesses.
|
28 |
+
Return the skills as a JSON list on 1 line, do not add newlines or any other text.
|
29 |
+
|
30 |
+
```
|
31 |
+
{resume}
|
32 |
+
```
|
33 |
+
"""
|
34 |
+
|
35 |
+
prompt_resume_skills = ChatPromptTemplate.from_template(
|
36 |
+
template=template_resume_skills
|
37 |
+
)
|
38 |
+
resume_skills = LLMChain(
|
39 |
+
llm=llm, prompt=prompt_resume_skills, output_key="resume_skills_predicted"
|
40 |
+
)
|
41 |
+
return resume_skills
|
42 |
+
|
43 |
+
|
44 |
+
def get_skills_intersection_chain(llm) -> LLMChain:
|
45 |
+
"""
|
46 |
+
# deprecated prompt:
|
47 |
+
|
48 |
+
# Can you return the intersection of the skills above delimited by backticks with the list of skills below delimited by backticks.
|
49 |
+
# Consider skills that are not exact but are close to each other in terms of meaning or usage.
|
50 |
+
# For example, 'Python programming' and 'Python' should be considered a match. Similarly, 'Strong problem-solving skills' and 'problem solver' should be considered the same.
|
51 |
+
# Please consider all skills in lowercase for matching. We're trying to match the skills of a job candidate (second list) with the requirements of a job vacancy (first list).
|
52 |
+
# Please keep this context in mind while performing the matching.
|
53 |
+
# If no skills match do not make up a response and return an empty list.
|
54 |
+
# Return the intersection as a JSON list on 1 line, do not add newlines or any other text.
|
55 |
+
"""
|
56 |
+
template_get_skills_intersection = """
|
57 |
+
|
58 |
+
```
|
59 |
+
{vacancy_skills_predicted}
|
60 |
+
```
|
61 |
+
|
62 |
+
Can you return the intersection of the skills above delimited by backticks with the list of skills below delimited by backticks.
|
63 |
+
Consider skills that are not exact but are close to each other in terms of meaning or usage. For example, 'Python programming' and 'Python' should be considered a match. Similarly, 'TensorFlow machine learning' and 'Machine Learning with TensorFlow' should be considered the same. Please consider all skills in lowercase for matching. We're trying to match the skills of a job candidate (second list) with the requirements of a job vacancy (first list). Please keep this context in mind while performing the matching.
|
64 |
+
If no skills match do not make up a response and return an empty list.
|
65 |
+
Return the intersection as a JSON list on 1 line, do not add newlines or any other text.
|
66 |
+
|
67 |
+
```
|
68 |
+
{resume_skills_predicted}
|
69 |
+
```
|
70 |
+
"""
|
71 |
+
|
72 |
+
prompt_get_skills_intersection = ChatPromptTemplate.from_template(
|
73 |
+
template=template_get_skills_intersection
|
74 |
+
)
|
75 |
+
skills_intersection = LLMChain(
|
76 |
+
llm=llm,
|
77 |
+
prompt=prompt_get_skills_intersection,
|
78 |
+
output_key="skills_intersection_predicted",
|
79 |
+
)
|
80 |
+
return skills_intersection
|
81 |
+
|
82 |
+
|
83 |
+
def get_skills_chain(llm) -> SequentialChain:
|
84 |
+
vacancy_skills_chain = get_vacancy_skills_chain(llm=llm)
|
85 |
+
resume_skills_chain = get_resume_skills_chain(llm=llm)
|
86 |
+
intersection_skills_chain = get_skills_intersection_chain(llm=llm)
|
87 |
+
|
88 |
+
return SequentialChain(
|
89 |
+
chains=[vacancy_skills_chain, resume_skills_chain, intersection_skills_chain],
|
90 |
+
input_variables=["vacancy", "resume"],
|
91 |
+
output_variables=[
|
92 |
+
vacancy_skills_chain.output_key,
|
93 |
+
resume_skills_chain.output_key,
|
94 |
+
intersection_skills_chain.output_key,
|
95 |
+
],
|
96 |
+
verbose=False,
|
97 |
+
)
|
utils.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
|
4 |
+
def get_intersection(a: list, b: list) -> list:
|
5 |
+
a = [i.lower() for i in a]
|
6 |
+
b = [i.lower() for i in b]
|
7 |
+
return sorted(set(a).intersection(set(b)))
|
8 |
+
|
9 |
+
|
10 |
+
def not_in_intersection(a: list, b: list) -> list:
|
11 |
+
return sorted(set(a).union(set(b)).difference(set(a).intersection(b)))
|
12 |
+
|
13 |
+
|
14 |
+
def get_score(true_values: list, predicted_values: list) -> float:
|
15 |
+
intersection_list = get_intersection(true_values, predicted_values)
|
16 |
+
return len(intersection_list) / len(true_values) if len(true_values) else 0
|
17 |
+
|
18 |
+
|
19 |
+
def match(present_features, not_present_features):
|
20 |
+
relevant_skills = len(present_features)
|
21 |
+
total_skills = len(present_features + not_present_features)
|
22 |
+
match = round(100.0 * (relevant_skills / total_skills), 2)
|
23 |
+
return match
|
24 |
+
|
25 |
+
|
26 |
+
def get_json_list_from_result(result: dict, key: str) -> list:
|
27 |
+
try:
|
28 |
+
return json.loads(result[key].strip())
|
29 |
+
except json.decoder.JSONDecodeError:
|
30 |
+
print(key, result[key])
|
31 |
+
return []
|