Vincent Claes commited on
Commit
a1372cb
1 Parent(s): f54a323

jobfixers create HTML table

Browse files
Files changed (6) hide show
  1. app.py +100 -69
  2. recruiting_assistant.py +0 -2
  3. scripts/README.md +5 -0
  4. scripts/scrape_website.py +179 -0
  5. skills.py +97 -0
  6. 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 zoek_vacature(input_text):
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
- # Voeg een nieuwe regel toe na elke '.' en voeg "Vacature <volgnummer>:\n" toe voor elk item
23
- updated_prediction = [
24
- f"Vacature {i + 1}:\n=============================\n{s}"
25
- for i, s in enumerate(prediction)
26
- ]
27
- updated_prediction = [s.replace(". ", ".\n") for s in updated_prediction]
28
- updated_prediction = [s.replace("•", "\n - ") for s in updated_prediction]
29
- return "\n\n".join(updated_prediction)
30
- return "niets teruggevonden ..."
31
-
32
- examples =[[
33
- """
34
- Naam: John Doe
35
- Adres: Parkstraat 123, 1000 Stadsveld
36
- Telefoon: +31 6 1234 5678
37
- E-mail: john.doe@email.com
38
- Geboortedatum: 15 juli 1985
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
- Werkervaring:
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
- Nederlands: Moedertaal
72
- Engels: Goed
73
- Referenties:
74
- Beschikbaar op verzoek.
75
 
76
- 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.
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
- mobile_collapse=False, equal_height=True
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
- text_search_result = gr.Textbox(
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=zoek_vacature,
142
  inputs=text_cv,
143
- outputs=text_search_result,
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: john.doe@email.com
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 []