From aacd87f1ab160647786d4d153a0ecd96f7fea1a3 Mon Sep 17 00:00:00 2001 From: Erkan Celen Date: Sun, 4 Jun 2023 21:06:49 +0200 Subject: [PATCH 1/3] new gpt cv conversion functionality --- functions.py | 6 +- generate_cv.py | 2 +- gpt/gpt.py | 23 +++ gpt/gpt_functions.py | 176 ++++++++++++++++++ gpt/gpt_requirements.txt | 7 + gpt/questions.py | 25 +++ gpt/test_files/john_smith_cv.pdf | Bin 0 -> 75702 bytes .../john_smith_new_xebia_data_cv.yml | 52 ++++++ requirements.txt | 1 + streamlit_app.py | 2 +- 10 files changed, 287 insertions(+), 7 deletions(-) create mode 100644 gpt/gpt.py create mode 100644 gpt/gpt_functions.py create mode 100644 gpt/gpt_requirements.txt create mode 100644 gpt/questions.py create mode 100644 gpt/test_files/john_smith_cv.pdf create mode 100644 gpt/yaml_output/john_smith_new_xebia_data_cv.yml diff --git a/functions.py b/functions.py index e28322b..97dc08d 100644 --- a/functions.py +++ b/functions.py @@ -164,7 +164,7 @@ def size_checker(y: int, text): y = size_checker(y, text=exp["technologies"]) y -= 25 print(y) - if y < 0: + if y < 50: return True else: return False @@ -280,10 +280,6 @@ def generate_pptx_from_pdf( def yaml_checker(yaml): - """ - This function is used to assess if the current cv page will be enough - to display the next experience block. If not, a new page should be created. - """ assert ( "first_name" in yaml diff --git a/generate_cv.py b/generate_cv.py index 9d9079a..b7fcb5c 100644 --- a/generate_cv.py +++ b/generate_cv.py @@ -6,7 +6,7 @@ import yaml -def generate_cv(yaml_input: str = None, image=None): +def generate_cv(yaml_input:str=None, image=None): ## CLEANUP FILES cleanup_files(directories=["cv_pages", "cv_images"]) diff --git a/gpt/gpt.py b/gpt/gpt.py new file mode 100644 index 0000000..ca4ad1e --- /dev/null +++ b/gpt/gpt.py @@ -0,0 +1,23 @@ +from gpt_functions import pdf_reader, gpt_communicator, cv_text_to_yaml +import os +from datetime import datetime + +cv_text = pdf_reader('gpt/test_files/john_smith_cv.pdf') +output_file_name = "john_smith_new_xebia_data_cv" + +cv_text_to_yaml(cv_text=cv_text, output_dir="./gpt/yaml_output", output_file_name=output_file_name) + +# input_directory = "./gpt/pdf" +# for filename in os.listdir(input_directory): +# try: +# f = os.path.join(input_directory, filename) +# print(f) +# print(f"starting {filename}") +# cv_text = pdf_reader(f) +# output_file_name = filename.replace(".pdf", "") +# cv_text_to_yaml(cv_text=cv_text, output_dir="./gpt/yaml_output", output_file_name=output_file_name) +# now = datetime.now() +# print(f"{filename} completed at {now}") +# except: +# print(f"{filename} FAILED at {now}") +# continue diff --git a/gpt/gpt_functions.py b/gpt/gpt_functions.py new file mode 100644 index 0000000..965c8d3 --- /dev/null +++ b/gpt/gpt_functions.py @@ -0,0 +1,176 @@ +import openai +import fitz +import pytesseract +import cv2 +import tempfile +import os +from dotenv import load_dotenv +from questions import questions +import time + +load_dotenv() +deployment_name = os.getenv("DEPLOYMENT-NAME") +openai.api_type = "azure" +openai.api_version = "2023-03-15-preview" +openai.api_base = os.getenv("ENDPOINT") # Your Azure OpenAI resource's endpoint value. +openai.api_key = os.getenv("API-KEY") + +def pdf_reader(pdf_path:str=None): + doc = fitz.open(pdf_path) + text = "" + pages = doc.pages() + for page in pages: + with tempfile.TemporaryDirectory() as tmpdir: + # page = doc.load_page(0) # number of page + pix = page.get_pixmap(dpi=300) + path = f"{tmpdir}/image.jpg" + pix.save(path) + + img = cv2.imread(path) + img_text = pytesseract.image_to_string(img) + text = text + "\n" + img_text + doc.close() + print(f"extracted text from {pdf_path}" + "\n") + return text + +def gpt_communicator(text:str=None, question:str=None, verbose=True): + response = openai.ChatCompletion.create( + engine=deployment_name, # The deployment name you chose when you deployed the ChatGPT or GPT-4 model. + messages=[ + {"role": "user", "content": f""" + \"{text}\" + + Based on the CV above: + {question} + """ + } + ] + ) + + answer = response['choices'][0]['message']['content'] + if verbose: + print(question) + print(f"===> {answer}") + print('----------------\n') + time.sleep(3) + return answer + +def cv_text_to_yaml(cv_text:str=None, output_dir:str="../gpt/yaml_output", output_file_name:str=None): + yaml_text = "" + text = cv_text + + first_name = gpt_communicator(text=text, question=questions['first_name']).strip() + yaml_text += '\n' + f'first_name: "{first_name}"' + + last_name = gpt_communicator(text, questions['last_name']).strip() + yaml_text += '\n' + f'last_name: "{last_name}"' + + role = gpt_communicator(text, questions['role']).strip() + yaml_text += '\n' + f'role: "{role}"' + + email_address = gpt_communicator(text, questions['email_address']).strip() + if email_address == "None": + yaml_text += '\n' + f'email: ' + else: + yaml_text += '\n' + f'email: "{email_address}"' + + phone_number = gpt_communicator(text, questions['phone_number']).strip() + if phone_number == "None": + yaml_text += '\n' + f'phone: ' + else: + yaml_text += '\n' + f'phone: "{phone_number}"' + + linkedin = gpt_communicator(text, questions['linkedin']).strip() + if linkedin == "None": + yaml_text += '\n' + f'linkedin: ' + else: + yaml_text += '\n' + f'linkedin: "{linkedin}"' + + github = gpt_communicator(text, questions['github']).strip() + if github == "None": + yaml_text += '\n' + f'github: ' + else: + yaml_text += '\n' + f'github: "{github}"' + + # website = gpt_communicator(text, questions['website']).strip() + # if website == "None": + # yaml_text += '\n' + f'website: ' + # else: + # yaml_text += '\n' + f'website: "{website}"' + + about_me = gpt_communicator(text, questions['about_me']).replace('\n', ' ').replace(' ', ' ').strip() + yaml_text += '\n' + f'about_me: "{about_me}"' + + education_degrees = gpt_communicator(text, questions['education_degrees']).strip() + yaml_text += '\n' + f'education:' + for degree in education_degrees.split(','): + degree = degree.strip() + education_year = gpt_communicator(text, questions['education_year'].format(degree=degree)).strip() + education_school = gpt_communicator(text, questions['education_school'].format(degree=degree)).strip() + yaml_text += '\n' + f' - degree: "{degree}"' + if education_school == "None": + yaml_text += '\n' + f' institution: ' + else: + yaml_text += '\n' + f' institution: "{education_school}"' + if education_year == "None": + yaml_text += '\n' + f' year: ' + else: + yaml_text += '\n' + f' year: "{education_year}"' + + biography = gpt_communicator(text, questions['biography']).replace('\n', ' ').replace(' ', ' ').strip() + yaml_text += '\n' + f'biography: "{biography}"' + + roles = gpt_communicator(text, questions['roles']).strip() + yaml_text += '\n' + f'roles:' + for role in roles.split(','): + role = role.strip() + role_description = gpt_communicator(text, questions['role_description'].format(role=role)).strip() + yaml_text += '\n' + f' - title: "{role}"' + yaml_text += '\n' + f' description: "{role_description}"' + + certifications = gpt_communicator(text, questions['certifications']).strip() + yaml_text += '\n' + f'certifications:' + if certifications != "None": + for certification in certifications.split(","): + certification = certification.strip() + yaml_text += '\n' + f' - title: "{certification}"' + + competences_titles = gpt_communicator(text, questions['competences_titles']).strip() + yaml_text += '\n' + f'competences:' + for competences_title in competences_titles.split(','): + competences_title = competences_title.strip() + competences = gpt_communicator(text, questions['competences'].format(competences_title=competences_title)).strip() + yaml_text += '\n' + f' - title: "{competences_title}"' + yaml_text += '\n' + f' description: "{competences}"' + + companies = gpt_communicator(text, questions['companies']).strip() + yaml_text += '\n' + f'experience:' + for company in companies.split(','): + company = company.strip() + company_role = gpt_communicator(text, questions['company_role'].format(company=company)).strip() + company_start = gpt_communicator(text, questions['company_start'].format(company=company)).strip() + company_end = gpt_communicator(text, questions['company_end'].format(company=company)).strip() + company_work = gpt_communicator(text, questions['company_work'].format(company=company)).strip().replace("\n", "\n ").replace("¢", "•").replace("* ", "• ").replace("+ ", "• ").replace("« ", "• ") + company_technologies = gpt_communicator(text, questions['company_technologies'].format(company=company)).strip() + + yaml_text += '\n' + f' - title: "{company_role}"' + yaml_text += '\n' + f' company: "{company}"' + if company_start == "None": + yaml_text += '\n' + f' start: ' + else: + yaml_text += '\n' + f' start: "{company_start}"' + if company_end == "None": + yaml_text += '\n' + f' end: ' + else: + yaml_text += '\n' + f' end: "{company_end}"' + yaml_text += '\n' + f' description: "{company_work}"' + if company_technologies == "None": + yaml_text += '\n' + f' technologies: ' + else: + yaml_text += '\n' + f' technologies: "{company_technologies}"' + yaml_text += '\n' + f' visible: true' + + with open(f"{output_dir}/{output_file_name}.yml", "w") as text_file: + text_file.write(yaml_text) + + return yaml_text diff --git a/gpt/gpt_requirements.txt b/gpt/gpt_requirements.txt new file mode 100644 index 0000000..683a451 --- /dev/null +++ b/gpt/gpt_requirements.txt @@ -0,0 +1,7 @@ +openai +python-dotenv +pypdf +opencv-python +pytesseract +pdf2image +ppt2pdf diff --git a/gpt/questions.py b/gpt/questions.py new file mode 100644 index 0000000..b3c9238 --- /dev/null +++ b/gpt/questions.py @@ -0,0 +1,25 @@ +questions = { + 'first_name' : """What's this persons first name? Give me only the name.""", + 'last_name' : """What's this persons last name? Give me only the name.""", + 'role' : """What's this persons main role? Don't tell me "Data Analyst". Your answer must be one of these titles: "Analytics Engineer", "Machine Learning Engineer", "Data Scientist", "Data Engineer", "Analytics Translator". Give me only the title.""", + 'about_me' : """What does this persons "About Me" section say? If you cannot find an "About Me" section, generate an appropriate one based on this persons CV. Keep it short and informal, no more than 75 words. Give me only the text.""", + 'education_degrees' : """What are this persons education degrees? Remove commas in degree names if any. Give me only the degree names, seperated by commas. Order them from newest to oldest. If not provided, answer "None". """, + 'education_year' : """When did they study {degree}? Give me your answer in a format like "2000 - 2004". If not provided, answer "None".""", + 'education_school' : """What is the name of the school they studied {degree}? Give me only the school name. If not provided, answer "None".""", + 'biography' : """What does this persons "Biography" section says? If you cannot find a "Biography" section, generate an appropriate one based on this persons CV. Keep it short, no more than 100 words. Give me only the text.""", + 'roles' : """What roles are listed in the Roles section of the CV? Give me no more than 4 role titles. If not provided in the CV, give me 3 appropriate role titles. Don't include company names or the work they do, give only generic work titles like Data Engineer, Data Analyst, Data Scientist etc. Give me the role titles, seperated by commas.""", + 'role_description' : """What is the description of their {role} role in the CV? If they did not provide this in the CV, write an appropriate one, about 20 words long. Keep it generic, not specific for a company they worked for. Give me only the text.""", + 'certifications' : """What certifications does this person have? Give me only the certifications, seperated by commas. If not provided, answer "None".""", + 'competences_titles' : """What are the competences titles listed in the Competences section of the CV? Such as "Programming, Tech, Languages". Don't give me specific competences like SQL, Python etc, give me more generic titles like Programming, Cloud, Languages. Don't include "Education" and "Certification" in these titles. Give me only the titles, seperated by commas. If competences titles are not explicitly listed in the CV, select applicable ones from "Programming, Data Stack, Visualization, Cloud, Tech, Languages" """, + 'competences' : """What are this persons competences that would fall under {competences_title}? Give me only the competences, seperated by commas.""", + 'companies' : """What companies did this person work for? Remove commas in company names if any. Give me only the company names, seperated by commas. Order them from newest to oldest.""", + 'company_role' : """What was this persons job title at {company}? Don't include what they do, or company name etc. Give me a generic job title like Data Engineer, Analytics Engineer, Data Scientist, Machine Learning Engineer etc. If the job title is not explicitly provided in the CV, try to come up with an appropriate one. Give me only the job title.""", + 'company_start' : """When did this person started working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If not provided, answer "None".""", + 'company_end' : """When did this person finished working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If it says present, answer "Present". If nothing about end date is provided, answer "None".""", + 'company_work' : """What did this person do at {company}? Give me the text block exactly as it is written in the CV, don't add anything yourself, remove the technologies that are mentioned in the end if they are mentioned. Don't include company name, role title or year/month they worked there if they are mentioned in the text. If there are stange characters in the original text, like "¢, *, +, «", replace them with "•" if you think they are meant to be bulletpoints.""", + 'company_technologies' : """What technologies did this person use at {company}? These should be provided in the CV, if not, write related technologies yourself. Give me only the names of technologies, seperated by commas. If you cannot find related technologies, answer "None".""", + 'email_address' : """What's this persons email address? Give me only the address. If not provided, answer "None".""", + 'phone_number' : """What's this persons phone number? Give me only the number. If not provided, answer "None".""", + 'linkedin' : """What's this persons linkedin address? Give me only the address, cut everything before "linkedin" in the address. If not provided, answer "None".""", + 'github' : """What's this persons github address? Give me only the address, cut everything before "github" in the address. If not provided, answer "None".""", +} diff --git a/gpt/test_files/john_smith_cv.pdf b/gpt/test_files/john_smith_cv.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0e7086b1b3fddd633f5aa4fd960f4e624a44e03e GIT binary patch literal 75702 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 jRz}t&|ETr) Date: Sun, 4 Jun 2023 21:23:11 +0200 Subject: [PATCH 2/3] pre-commit black --- functions.py | 1 - generate_cv.py | 2 +- gpt/gpt.py | 10 +-- gpt/gpt_functions.py | 201 ++++++++++++++++++++++++++----------------- gpt/questions.py | 46 +++++----- 5 files changed, 151 insertions(+), 109 deletions(-) diff --git a/functions.py b/functions.py index 97dc08d..44daf6f 100644 --- a/functions.py +++ b/functions.py @@ -280,7 +280,6 @@ def generate_pptx_from_pdf( def yaml_checker(yaml): - assert ( "first_name" in yaml ), "'first_name' field is missing in yaml. this is a mandatory field." diff --git a/generate_cv.py b/generate_cv.py index b7fcb5c..9d9079a 100644 --- a/generate_cv.py +++ b/generate_cv.py @@ -6,7 +6,7 @@ import yaml -def generate_cv(yaml_input:str=None, image=None): +def generate_cv(yaml_input: str = None, image=None): ## CLEANUP FILES cleanup_files(directories=["cv_pages", "cv_images"]) diff --git a/gpt/gpt.py b/gpt/gpt.py index ca4ad1e..c383c85 100644 --- a/gpt/gpt.py +++ b/gpt/gpt.py @@ -1,11 +1,11 @@ -from gpt_functions import pdf_reader, gpt_communicator, cv_text_to_yaml -import os -from datetime import datetime +from gpt_functions import pdf_reader, cv_text_to_yaml -cv_text = pdf_reader('gpt/test_files/john_smith_cv.pdf') +cv_text = pdf_reader("gpt/test_files/john_smith_cv.pdf") output_file_name = "john_smith_new_xebia_data_cv" -cv_text_to_yaml(cv_text=cv_text, output_dir="./gpt/yaml_output", output_file_name=output_file_name) +cv_text_to_yaml( + cv_text=cv_text, output_dir="./gpt/yaml_output", output_file_name=output_file_name +) # input_directory = "./gpt/pdf" # for filename in os.listdir(input_directory): diff --git a/gpt/gpt_functions.py b/gpt/gpt_functions.py index 965c8d3..41b18ad 100644 --- a/gpt/gpt_functions.py +++ b/gpt/gpt_functions.py @@ -11,11 +11,12 @@ load_dotenv() deployment_name = os.getenv("DEPLOYMENT-NAME") openai.api_type = "azure" -openai.api_version = "2023-03-15-preview" +openai.api_version = "2023-03-15-preview" openai.api_base = os.getenv("ENDPOINT") # Your Azure OpenAI resource's endpoint value. openai.api_key = os.getenv("API-KEY") -def pdf_reader(pdf_path:str=None): + +def pdf_reader(pdf_path: str = None): doc = fitz.open(pdf_path) text = "" pages = doc.pages() @@ -33,64 +34,72 @@ def pdf_reader(pdf_path:str=None): print(f"extracted text from {pdf_path}" + "\n") return text -def gpt_communicator(text:str=None, question:str=None, verbose=True): + +def gpt_communicator(text: str = None, question: str = None, verbose=True): response = openai.ChatCompletion.create( - engine=deployment_name, # The deployment name you chose when you deployed the ChatGPT or GPT-4 model. + engine=deployment_name, # The deployment name you chose when you deployed the ChatGPT or GPT-4 model. messages=[ - {"role": "user", "content": f""" + { + "role": "user", + "content": f""" \"{text}\" - Based on the CV above: + Based on the CV above: {question} - """ + """, } - ] + ], ) - answer = response['choices'][0]['message']['content'] + answer = response["choices"][0]["message"]["content"] if verbose: print(question) print(f"===> {answer}") - print('----------------\n') + print("----------------\n") time.sleep(3) return answer -def cv_text_to_yaml(cv_text:str=None, output_dir:str="../gpt/yaml_output", output_file_name:str=None): + +def cv_text_to_yaml( + cv_text: str = None, + output_dir: str = "../gpt/yaml_output", + output_file_name: str = None, +): yaml_text = "" text = cv_text - first_name = gpt_communicator(text=text, question=questions['first_name']).strip() - yaml_text += '\n' + f'first_name: "{first_name}"' + first_name = gpt_communicator(text=text, question=questions["first_name"]).strip() + yaml_text += "\n" + f'first_name: "{first_name}"' - last_name = gpt_communicator(text, questions['last_name']).strip() - yaml_text += '\n' + f'last_name: "{last_name}"' + last_name = gpt_communicator(text, questions["last_name"]).strip() + yaml_text += "\n" + f'last_name: "{last_name}"' - role = gpt_communicator(text, questions['role']).strip() - yaml_text += '\n' + f'role: "{role}"' + role = gpt_communicator(text, questions["role"]).strip() + yaml_text += "\n" + f'role: "{role}"' - email_address = gpt_communicator(text, questions['email_address']).strip() + email_address = gpt_communicator(text, questions["email_address"]).strip() if email_address == "None": - yaml_text += '\n' + f'email: ' + yaml_text += "\n" + f"email: " else: - yaml_text += '\n' + f'email: "{email_address}"' + yaml_text += "\n" + f'email: "{email_address}"' - phone_number = gpt_communicator(text, questions['phone_number']).strip() + phone_number = gpt_communicator(text, questions["phone_number"]).strip() if phone_number == "None": - yaml_text += '\n' + f'phone: ' + yaml_text += "\n" + f"phone: " else: - yaml_text += '\n' + f'phone: "{phone_number}"' + yaml_text += "\n" + f'phone: "{phone_number}"' - linkedin = gpt_communicator(text, questions['linkedin']).strip() + linkedin = gpt_communicator(text, questions["linkedin"]).strip() if linkedin == "None": - yaml_text += '\n' + f'linkedin: ' + yaml_text += "\n" + f"linkedin: " else: - yaml_text += '\n' + f'linkedin: "{linkedin}"' + yaml_text += "\n" + f'linkedin: "{linkedin}"' - github = gpt_communicator(text, questions['github']).strip() + github = gpt_communicator(text, questions["github"]).strip() if github == "None": - yaml_text += '\n' + f'github: ' + yaml_text += "\n" + f"github: " else: - yaml_text += '\n' + f'github: "{github}"' + yaml_text += "\n" + f'github: "{github}"' # website = gpt_communicator(text, questions['website']).strip() # if website == "None": @@ -98,77 +107,111 @@ def cv_text_to_yaml(cv_text:str=None, output_dir:str="../gpt/yaml_output", outpu # else: # yaml_text += '\n' + f'website: "{website}"' - about_me = gpt_communicator(text, questions['about_me']).replace('\n', ' ').replace(' ', ' ').strip() - yaml_text += '\n' + f'about_me: "{about_me}"' + about_me = ( + gpt_communicator(text, questions["about_me"]) + .replace("\n", " ") + .replace(" ", " ") + .strip() + ) + yaml_text += "\n" + f'about_me: "{about_me}"' - education_degrees = gpt_communicator(text, questions['education_degrees']).strip() - yaml_text += '\n' + f'education:' - for degree in education_degrees.split(','): + education_degrees = gpt_communicator(text, questions["education_degrees"]).strip() + yaml_text += "\n" + f"education:" + for degree in education_degrees.split(","): degree = degree.strip() - education_year = gpt_communicator(text, questions['education_year'].format(degree=degree)).strip() - education_school = gpt_communicator(text, questions['education_school'].format(degree=degree)).strip() - yaml_text += '\n' + f' - degree: "{degree}"' + education_year = gpt_communicator( + text, questions["education_year"].format(degree=degree) + ).strip() + education_school = gpt_communicator( + text, questions["education_school"].format(degree=degree) + ).strip() + yaml_text += "\n" + f' - degree: "{degree}"' if education_school == "None": - yaml_text += '\n' + f' institution: ' + yaml_text += "\n" + f" institution: " else: - yaml_text += '\n' + f' institution: "{education_school}"' + yaml_text += "\n" + f' institution: "{education_school}"' if education_year == "None": - yaml_text += '\n' + f' year: ' + yaml_text += "\n" + f" year: " else: - yaml_text += '\n' + f' year: "{education_year}"' + yaml_text += "\n" + f' year: "{education_year}"' - biography = gpt_communicator(text, questions['biography']).replace('\n', ' ').replace(' ', ' ').strip() - yaml_text += '\n' + f'biography: "{biography}"' + biography = ( + gpt_communicator(text, questions["biography"]) + .replace("\n", " ") + .replace(" ", " ") + .strip() + ) + yaml_text += "\n" + f'biography: "{biography}"' - roles = gpt_communicator(text, questions['roles']).strip() - yaml_text += '\n' + f'roles:' - for role in roles.split(','): + roles = gpt_communicator(text, questions["roles"]).strip() + yaml_text += "\n" + f"roles:" + for role in roles.split(","): role = role.strip() - role_description = gpt_communicator(text, questions['role_description'].format(role=role)).strip() - yaml_text += '\n' + f' - title: "{role}"' - yaml_text += '\n' + f' description: "{role_description}"' - - certifications = gpt_communicator(text, questions['certifications']).strip() - yaml_text += '\n' + f'certifications:' + role_description = gpt_communicator( + text, questions["role_description"].format(role=role) + ).strip() + yaml_text += "\n" + f' - title: "{role}"' + yaml_text += "\n" + f' description: "{role_description}"' + + certifications = gpt_communicator(text, questions["certifications"]).strip() + yaml_text += "\n" + f"certifications:" if certifications != "None": for certification in certifications.split(","): certification = certification.strip() - yaml_text += '\n' + f' - title: "{certification}"' + yaml_text += "\n" + f' - title: "{certification}"' - competences_titles = gpt_communicator(text, questions['competences_titles']).strip() - yaml_text += '\n' + f'competences:' - for competences_title in competences_titles.split(','): + competences_titles = gpt_communicator(text, questions["competences_titles"]).strip() + yaml_text += "\n" + f"competences:" + for competences_title in competences_titles.split(","): competences_title = competences_title.strip() - competences = gpt_communicator(text, questions['competences'].format(competences_title=competences_title)).strip() - yaml_text += '\n' + f' - title: "{competences_title}"' - yaml_text += '\n' + f' description: "{competences}"' - - companies = gpt_communicator(text, questions['companies']).strip() - yaml_text += '\n' + f'experience:' - for company in companies.split(','): + competences = gpt_communicator( + text, questions["competences"].format(competences_title=competences_title) + ).strip() + yaml_text += "\n" + f' - title: "{competences_title}"' + yaml_text += "\n" + f' description: "{competences}"' + + companies = gpt_communicator(text, questions["companies"]).strip() + yaml_text += "\n" + f"experience:" + for company in companies.split(","): company = company.strip() - company_role = gpt_communicator(text, questions['company_role'].format(company=company)).strip() - company_start = gpt_communicator(text, questions['company_start'].format(company=company)).strip() - company_end = gpt_communicator(text, questions['company_end'].format(company=company)).strip() - company_work = gpt_communicator(text, questions['company_work'].format(company=company)).strip().replace("\n", "\n ").replace("¢", "•").replace("* ", "• ").replace("+ ", "• ").replace("« ", "• ") - company_technologies = gpt_communicator(text, questions['company_technologies'].format(company=company)).strip() - - yaml_text += '\n' + f' - title: "{company_role}"' - yaml_text += '\n' + f' company: "{company}"' + company_role = gpt_communicator( + text, questions["company_role"].format(company=company) + ).strip() + company_start = gpt_communicator( + text, questions["company_start"].format(company=company) + ).strip() + company_end = gpt_communicator( + text, questions["company_end"].format(company=company) + ).strip() + company_work = ( + gpt_communicator(text, questions["company_work"].format(company=company)) + .strip() + .replace("\n", "\n ") + .replace("¢", "•") + .replace("* ", "• ") + .replace("+ ", "• ") + .replace("« ", "• ") + ) + company_technologies = gpt_communicator( + text, questions["company_technologies"].format(company=company) + ).strip() + + yaml_text += "\n" + f' - title: "{company_role}"' + yaml_text += "\n" + f' company: "{company}"' if company_start == "None": - yaml_text += '\n' + f' start: ' + yaml_text += "\n" + f" start: " else: - yaml_text += '\n' + f' start: "{company_start}"' + yaml_text += "\n" + f' start: "{company_start}"' if company_end == "None": - yaml_text += '\n' + f' end: ' + yaml_text += "\n" + f" end: " else: - yaml_text += '\n' + f' end: "{company_end}"' - yaml_text += '\n' + f' description: "{company_work}"' + yaml_text += "\n" + f' end: "{company_end}"' + yaml_text += "\n" + f' description: "{company_work}"' if company_technologies == "None": - yaml_text += '\n' + f' technologies: ' + yaml_text += "\n" + f" technologies: " else: - yaml_text += '\n' + f' technologies: "{company_technologies}"' - yaml_text += '\n' + f' visible: true' + yaml_text += "\n" + f' technologies: "{company_technologies}"' + yaml_text += "\n" + f" visible: true" with open(f"{output_dir}/{output_file_name}.yml", "w") as text_file: text_file.write(yaml_text) diff --git a/gpt/questions.py b/gpt/questions.py index b3c9238..ba18566 100644 --- a/gpt/questions.py +++ b/gpt/questions.py @@ -1,25 +1,25 @@ questions = { - 'first_name' : """What's this persons first name? Give me only the name.""", - 'last_name' : """What's this persons last name? Give me only the name.""", - 'role' : """What's this persons main role? Don't tell me "Data Analyst". Your answer must be one of these titles: "Analytics Engineer", "Machine Learning Engineer", "Data Scientist", "Data Engineer", "Analytics Translator". Give me only the title.""", - 'about_me' : """What does this persons "About Me" section say? If you cannot find an "About Me" section, generate an appropriate one based on this persons CV. Keep it short and informal, no more than 75 words. Give me only the text.""", - 'education_degrees' : """What are this persons education degrees? Remove commas in degree names if any. Give me only the degree names, seperated by commas. Order them from newest to oldest. If not provided, answer "None". """, - 'education_year' : """When did they study {degree}? Give me your answer in a format like "2000 - 2004". If not provided, answer "None".""", - 'education_school' : """What is the name of the school they studied {degree}? Give me only the school name. If not provided, answer "None".""", - 'biography' : """What does this persons "Biography" section says? If you cannot find a "Biography" section, generate an appropriate one based on this persons CV. Keep it short, no more than 100 words. Give me only the text.""", - 'roles' : """What roles are listed in the Roles section of the CV? Give me no more than 4 role titles. If not provided in the CV, give me 3 appropriate role titles. Don't include company names or the work they do, give only generic work titles like Data Engineer, Data Analyst, Data Scientist etc. Give me the role titles, seperated by commas.""", - 'role_description' : """What is the description of their {role} role in the CV? If they did not provide this in the CV, write an appropriate one, about 20 words long. Keep it generic, not specific for a company they worked for. Give me only the text.""", - 'certifications' : """What certifications does this person have? Give me only the certifications, seperated by commas. If not provided, answer "None".""", - 'competences_titles' : """What are the competences titles listed in the Competences section of the CV? Such as "Programming, Tech, Languages". Don't give me specific competences like SQL, Python etc, give me more generic titles like Programming, Cloud, Languages. Don't include "Education" and "Certification" in these titles. Give me only the titles, seperated by commas. If competences titles are not explicitly listed in the CV, select applicable ones from "Programming, Data Stack, Visualization, Cloud, Tech, Languages" """, - 'competences' : """What are this persons competences that would fall under {competences_title}? Give me only the competences, seperated by commas.""", - 'companies' : """What companies did this person work for? Remove commas in company names if any. Give me only the company names, seperated by commas. Order them from newest to oldest.""", - 'company_role' : """What was this persons job title at {company}? Don't include what they do, or company name etc. Give me a generic job title like Data Engineer, Analytics Engineer, Data Scientist, Machine Learning Engineer etc. If the job title is not explicitly provided in the CV, try to come up with an appropriate one. Give me only the job title.""", - 'company_start' : """When did this person started working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If not provided, answer "None".""", - 'company_end' : """When did this person finished working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If it says present, answer "Present". If nothing about end date is provided, answer "None".""", - 'company_work' : """What did this person do at {company}? Give me the text block exactly as it is written in the CV, don't add anything yourself, remove the technologies that are mentioned in the end if they are mentioned. Don't include company name, role title or year/month they worked there if they are mentioned in the text. If there are stange characters in the original text, like "¢, *, +, «", replace them with "•" if you think they are meant to be bulletpoints.""", - 'company_technologies' : """What technologies did this person use at {company}? These should be provided in the CV, if not, write related technologies yourself. Give me only the names of technologies, seperated by commas. If you cannot find related technologies, answer "None".""", - 'email_address' : """What's this persons email address? Give me only the address. If not provided, answer "None".""", - 'phone_number' : """What's this persons phone number? Give me only the number. If not provided, answer "None".""", - 'linkedin' : """What's this persons linkedin address? Give me only the address, cut everything before "linkedin" in the address. If not provided, answer "None".""", - 'github' : """What's this persons github address? Give me only the address, cut everything before "github" in the address. If not provided, answer "None".""", + "first_name": """What's this persons first name? Give me only the name.""", + "last_name": """What's this persons last name? Give me only the name.""", + "role": """What's this persons main role? Don't tell me "Data Analyst". Your answer must be one of these titles: "Analytics Engineer", "Machine Learning Engineer", "Data Scientist", "Data Engineer", "Analytics Translator". Give me only the title.""", + "about_me": """What does this persons "About Me" section say? If you cannot find an "About Me" section, generate an appropriate one based on this persons CV. Keep it short and informal, no more than 75 words. Give me only the text.""", + "education_degrees": """What are this persons education degrees? Remove commas in degree names if any. Give me only the degree names, seperated by commas. Order them from newest to oldest. If not provided, answer "None". """, + "education_year": """When did they study {degree}? Give me your answer in a format like "2000 - 2004". If not provided, answer "None".""", + "education_school": """What is the name of the school they studied {degree}? Give me only the school name. If not provided, answer "None".""", + "biography": """What does this persons "Biography" section says? If you cannot find a "Biography" section, generate an appropriate one based on this persons CV. Keep it short, no more than 100 words. Give me only the text.""", + "roles": """What roles are listed in the Roles section of the CV? Give me no more than 4 role titles. If not provided in the CV, give me 3 appropriate role titles. Don't include company names or the work they do, give only generic work titles like Data Engineer, Data Analyst, Data Scientist etc. Give me the role titles, seperated by commas.""", + "role_description": """What is the description of their {role} role in the CV? If they did not provide this in the CV, write an appropriate one, about 20 words long. Keep it generic, not specific for a company they worked for. Give me only the text.""", + "certifications": """What certifications does this person have? Give me only the certifications, seperated by commas. If not provided, answer "None".""", + "competences_titles": """What are the competences titles listed in the Competences section of the CV? Such as "Programming, Tech, Languages". Don't give me specific competences like SQL, Python etc, give me more generic titles like Programming, Cloud, Languages. Don't include "Education" and "Certification" in these titles. Give me only the titles, seperated by commas. If competences titles are not explicitly listed in the CV, select applicable ones from "Programming, Data Stack, Visualization, Cloud, Tech, Languages" """, + "competences": """What are this persons competences that would fall under {competences_title}? Give me only the competences, seperated by commas.""", + "companies": """What companies did this person work for? Remove commas in company names if any. Give me only the company names, seperated by commas. Order them from newest to oldest.""", + "company_role": """What was this persons job title at {company}? Don't include what they do, or company name etc. Give me a generic job title like Data Engineer, Analytics Engineer, Data Scientist, Machine Learning Engineer etc. If the job title is not explicitly provided in the CV, try to come up with an appropriate one. Give me only the job title.""", + "company_start": """When did this person started working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If not provided, answer "None".""", + "company_end": """When did this person finished working at {company}? Give me only the year and the month, in "2000 June" format. Don't write anything else! If it says present, answer "Present". If nothing about end date is provided, answer "None".""", + "company_work": """What did this person do at {company}? Give me the text block exactly as it is written in the CV, don't add anything yourself, remove the technologies that are mentioned in the end if they are mentioned. Don't include company name, role title or year/month they worked there if they are mentioned in the text. If there are stange characters in the original text, like "¢, *, +, «", replace them with "•" if you think they are meant to be bulletpoints.""", + "company_technologies": """What technologies did this person use at {company}? These should be provided in the CV, if not, write related technologies yourself. Give me only the names of technologies, seperated by commas. If you cannot find related technologies, answer "None".""", + "email_address": """What's this persons email address? Give me only the address. If not provided, answer "None".""", + "phone_number": """What's this persons phone number? Give me only the number. If not provided, answer "None".""", + "linkedin": """What's this persons linkedin address? Give me only the address, cut everything before "linkedin" in the address. If not provided, answer "None".""", + "github": """What's this persons github address? Give me only the address, cut everything before "github" in the address. If not provided, answer "None".""", } From 3402394e282b6d2226ed8e1ba775a203503bba7d Mon Sep 17 00:00:00 2001 From: Erkan Celen Date: Sun, 4 Jun 2023 21:29:00 +0200 Subject: [PATCH 3/3] staged example yaml for black --- .../john_smith_new_xebia_data_cv.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gpt/yaml_output/john_smith_new_xebia_data_cv.yml b/gpt/yaml_output/john_smith_new_xebia_data_cv.yml index 11d49c2..438e23e 100644 --- a/gpt/yaml_output/john_smith_new_xebia_data_cv.yml +++ b/gpt/yaml_output/john_smith_new_xebia_data_cv.yml @@ -4,14 +4,14 @@ last_name: "Smith" role: "Data Engineer" email: "email@email.com" phone: "3868683442" -linkedin: -github: -website: +linkedin: +github: +website: about_me: "Hi there! I'm a dedicated Data Engineer with over 5 years of experience working with large datasets and building robust databases. I'm passionate about utilizing my skills in SQL, Java, and Python to create game-changing insights for businesses. Besides work, you can find me cycling, songwriting, and running. Excited to see what new challenges lie ahead!" education: - degree: "BS Computer Science" institution: "Texas University" - year: + year: biography: "John Smith is a dedicated Data Engineer with over 5 years of experience working with large datasets. He has a proven track record of building robust databases and implementing natural language processing tools to support data scientists. John is highly skilled in SQL, Java, Apache Spark, Hadoop, and Python, and is fluent in English and German. He holds a Bachelor's degree in Computer Science from Texas University, with a dual concentration in Machine Learning and a Business Foundations Certificate. His passion for delivering game-changing insights extends to his hobbies, including cycling, songwriting, and running." roles: - title: "Data Engineer" @@ -40,13 +40,13 @@ experience: start: "2015 January" end: "2017 December" description: "Responsible for developing database triggers, packages, functions, and stored procedures using PL/SQL and maintain the scripts for various data feeds across multiple regional and international offices of the company - + • Co-develop a SQL server database system to maximize performance benefits for clientele. - + • Assisted senior-level Data Scientists in the design of ETL processes, including SSIS packages. - + • Developed coherent Logical Data Models that helped guide important client business decisions. - + • Collaborate and coordinate with development teams to deploy data quality solutions and create and maintain standard operating procedure documentation." technologies: "PL/SQL, SQL server, ETL processes, SSIS packages" - visible: true \ No newline at end of file + visible: true