Build an AI Career Skills Analyzer
Turn job postings into validated career skills reports with ChatGPT and Python.
Introduction
30 Second Summary
Job postings often scatter the skills you need across headings or vague role descriptions. That makes it hard to decide what to learn first.
In this project, you will use ChatGPT to turn a fictional AI job posting into structured JSON skills data. A Python program will validate every evidence quote before printing a career report.
What You'll Build
When you run your finished analyzer, the terminal shows a grouped career report only after the data passes its structure checks plus its evidence checks.
By the end of this project, you'll have:
- A reusable prompt that turns a job posting into a predictable JSON object for automation.
- A career-skills report that groups skills by category. Separate lines make required skills plus preferred skills easy to compare.
- An evidence-based quality gate that catches malformed JSON. It also rejects unsupported quotes or invalid labels.
- Secret Mission: Compare two AI roles to reveal the skills they share.
Are there any prerequisites?
You'll need a Mac with working access to ChatGPT, Visual Studio Code, plus Python. You do not need an API key or a paid service.
Before We Start
A career analyzer becomes useful when it connects a job posting to a role you genuinely want to understand. This quick commitment gives the fictional example a clear purpose before the hands-on work begins.
Confirm Your Workspace
You have chosen an AI role to investigate. Before AI output enters the project, you need one known folder for every input and result.
A single Visual Studio Code workspace keeps those files together. A Python 3 check proves the interpreter can run inside that workspace.
In this step, get ready to:
- Open ai-career-skills-analyzer as a Visual Studio Code workspace.
- Save the fictional posting in job_posting.txt.
- Confirm Python 3 can execute commands from the integrated terminal.
Create the workspace and starter file
A workspace gives the editor and terminal access to the same named folder. This keeps every later command focused on the project files.
- Press Cmd+Space to open Spotlight.
- Type Visual Studio Code and press Enter.
- Select File from the Visual Studio Code menu bar.
- Select Open Folder....
- Choose the Desktop as the folder location.
- Select New Folder.
- Enter ai-career-skills-analyzer as the folder name.
- Select Open.
Workspace Trust May Appear
Visual Studio Code may ask whether you trust the folder's authors. You created this empty folder yourself, so you can enable the workspace features.
- Select Yes, I trust the authors if the Workspace Trust prompt appears.
Your folder name now appears at the top of the Explorer sidebar. You have one clear home for the complete analyzer workflow.
- Select the Explorer view in the Activity Bar.
- Select the New File... button in the Explorer sidebar.
- Type job_posting.txt in the file name field.
- Press Enter to create the file.
- Add the fictional job posting to job_posting.txt by copying the content below:
Junior AI Automation Analyst
We are hiring a Junior AI Automation Analyst to improve internal team workflows.
Required skills:
- Basic Python
- Ability to work with JSON
- Clear written communication
In this role, you will use ChatGPT to draft and improve internal workflows, test outputs against defined acceptance criteria, and document results for non-technical teammates.
Preferred skills:
- Familiarity with Git
- Familiarity with spreadsheets
No prior machine learning research experience is required.
Why Use a Fixed Posting?
- The posting supplies a stable source for every prompt in this project.
- Its exact wording gives the analyzer evidence it can check later.
- Save job_posting.txt by pressing Cmd+S on macOS or Ctrl+S on Windows.
- Check the Explorer sidebar for job_posting.txt.
You'll see job_posting.txt inside ai-career-skills-analyzer. The first line in the editor reads Junior AI Automation Analyst.
File Missing From the Workspace?
- Confirm ai-career-skills-analyzer appears as the top folder in the Explorer sidebar.
- Move job_posting.txt into that folder from the Explorer sidebar if it appears elsewhere.
Help me place the starter file in the correct Visual Studio Code workspace.
✔️ Awesome, I've got everything!
Your fictional posting is saved in the workspace. Keep the file open for the comparison below.
ⓧ I'd like to double check the full code
Junior AI Automation Analyst
We are hiring a Junior AI Automation Analyst to improve internal team workflows.
Required skills:
- Basic Python
- Ability to work with JSON
- Clear written communication
In this role, you will use ChatGPT to draft and improve internal workflows, test outputs against defined acceptance criteria, and document results for non-technical teammates.
Preferred skills:
- Familiarity with Git
- Familiarity with spreadsheets
No prior machine learning research experience is required.
Confirm Python 3
The integrated terminal starts in the folder opened as your workspace. A version check confirms that its shell can find the Python 3 interpreter.
- Select Terminal from the Visual Studio Code menu bar.
- Select New Terminal.
- Check the available Python version by running this command:
python3 --version
What Does This Command Do?
- The python3 command starts the Python 3 interpreter on macOS.
- The --version flag prints the installed version number before the command exits.
✔️ Output Begins With Python 3
Your terminal can find Python 3 from this workspace. The output begins with Python 3 followed by the installed version number.
ⓧ Command Not Found
Python 3 is not available to the integrated terminal yet. The official macOS installer adds a Python 3 interpreter that the terminal can run.
- Visit the official Python releases for macOS.
- Select Download macOS installer for the latest stable release.
- Open the downloaded .pkg file.
- Complete the macOS installer with its default options.
- Switch back to the ai-career-skills-analyzer workspace from earlier.
- Select Terminal from the Visual Studio Code menu bar.
- Select New Terminal.
- Repeat the Python version check in the new terminal by running:
python3 --version
Why Use a New Terminal?
The new terminal starts a fresh shell after installation. The repeated command confirms that shell can now find Python 3.
You'll see output beginning with Python 3 followed by the installed version number.
Python Still Unavailable?
Close Visual Studio Code after the installer finishes. Reopen the workspace before repeating the version check in a new integrated terminal.
Help me troubleshoot why Python 3 is still unavailable in the VS Code terminal.
Prove Python runs in the workspace
The version check proves the interpreter exists. Executing a short statement confirms that Python can process a command from this workspace.
Before you run this, what exact message do you expect the interpreter to print?
- Prove the interpreter can execute a command by running:
python3 -c "print('AI career analyzer ready')"
What Does This Command Prove?
- The -c flag asks Python to execute the supplied statement.
- The print() call sends the readiness message to the terminal.
You'll see AI career analyzer ready on its own line. Your workspace can now run Python commands against the files you build here.
Readiness Message Missing?
- Confirm the terminal prompt belongs to the ai-career-skills-analyzer workspace.
- Copy the command again from the block above to avoid mismatched quotation marks.
Help me fix the Python readiness check in my workspace.
Your workspace now has a fixed source posting and a proven Python interpreter. Next, you'll test what happens when readable AI output meets software that expects structured data.
Test Unstructured Output
Your workspace can now run Python commands. The fictional job posting is ready for its first analysis pass.
A human-friendly ChatGPT answer can look useful while giving software no predictable structure. This step makes that gap visible by sending the response to a JSON parser.
In this step, get ready to:
- Ask ChatGPT for a prose-and-bullets skills summary.
- Save the complete response as skills.json.
- Test the response with Python's JSON validator.
Request an unstructured skills summary
The first prompt prioritizes readability. It asks ChatGPT for prose plus bullets without defining a machine-readable shape.
This uses the ChatGPT account you already have. No API key or paid API is involved.
- In job_posting.txt, select the entire fictional posting.
- Copy the selected posting.
- Go to ChatGPT in your browser.
- Sign in with your existing account.
- Start a new ChatGPT conversation.
- Paste the posting into ChatGPT's message field.
- Add a blank line below the posting.
- Append the instruction by copying this text:
What skills does this role need? Return a short paragraph followed by a bullet list. Do not use JSON.
What Does This Prompt Test?
The instruction asks for an answer that a person can scan quickly. It deliberately leaves out a structured output shape.
The final sentence keeps the response in prose and bullets. This gives the parser a clear test case.
- Submit the message to ChatGPT.
You should see a short paragraph followed by a bullet list of skills from the posting.
Save the response as skills.json
The .json extension labels a file as JSON. Its contents still come directly from the response you paste.
- Select the complete ChatGPT response.
- Copy the selected response.
- Switch back to VS Code.
- Select the ai-career-skills-analyzer folder in the Explorer sidebar.
- Use the new-file control beside the folder name to create skills.json.
- Paste the complete ChatGPT response into skills.json.
- Save skills.json in VS Code.
- Review skills.json in the editor.
You should see a human-readable paragraph followed by bullet points. The file should contain the complete ChatGPT response.
Did ChatGPT Return JSON Anyway?
ChatGPT responses can vary between conversations. A JSON object would remove the planned test case from this step.
- Start a fresh ChatGPT conversation.
- Send the job posting with the exact instruction shown above.
- Replace the contents of skills.json with the new prose-and-bullets response.
Ask for help if the response keeps ignoring the requested format: help me get a prose-and-bullets ChatGPT response without JSON.
Trigger the planned parsing failure
Python's json.tool module validates JSON before formatting it. This check reveals whether the file contents follow JSON syntax.
Before you run the check, do you think the file's name can make prose parse as JSON?
- Test skills.json with Python's JSON validator by running this command:
python3 -m json.tool skills.json
Why Did the Check Fail?
Python reads the contents of skills.json according to JSON syntax. The prose-and-bullets response does not follow that syntax.
Python reports a JSON parsing error instead of printing indented data. This planned failure proves that readable AI output can still break an automated workflow.
Seeing a Different Result?
- Replace skills.json if ChatGPT returned valid JSON.
- Move skills.json into the ai-career-skills-analyzer folder if Python cannot locate it.
- Run the validator command again after correcting the file.
Ask for help with an unexpected result: help me diagnose the output from the JSON validation command.
That failure is a win: you now have proof that readable AI output can break automation. Next, you'll give ChatGPT an exact JSON contract that Python can parse.
Generate Reliable JSON
The parsing error in your VS Code terminal proved that a file named skills.json can still contain invalid JSON. Software needs a predictable structure before it can use AI output.
Now you will give ChatGPT a precise prompt with context, constraints, evidence requirements, and an exact output shape. This contract turns the job posting into data that Python can parse.
In this step, get ready to:
- Build a structured-output contract in prompt.txt.
- Generate structured skills data with ChatGPT.
- Validate the new skills.json with Python.
Create the structured-output contract
A structured-output contract tells the model what source to use. It also defines the exact shape that the response must follow.
- In the VS Code file sidebar, use the file creation control to create prompt.txt inside ai-career-skills-analyzer.
- Add the context, instructions, and output shape to prompt.txt by pasting this first section:
# Context
You are extracting career skills from one job posting. The result will be parsed and checked by a Python program.
# Instructions
Identify only skills that are explicitly supported by the posting. For each skill, provide its name, category, importance, and an exact evidence quote copied from the posting.
# Output format
Return exactly one JSON object. Do not add commentary or Markdown code fences.
Use this shape:
{
"role_title": "string",
"skills": [
{
"name": "string",
"category": "technical | tool | domain | soft",
"importance": "required | preferred",
"evidence": "exact quote from the job posting"
}
],
"assumptions": []
}
What Does This Prompt Section Do?
- The # Context section explains how the response will be used.
- The # Instructions section limits the extraction to supported skills.
- The # Output format section defines the required fields.
- The allowed category values keep each skill inside a predictable label set.
- Save prompt.txt.
- Confirm the visible content ends with the closing brace below "assumptions": [].
Missing Part of the Output Shape?
Check that the opening brace has a matching closing brace. Confirm that the skills array contains one complete example skill object.
Ask for help checking the first prompt section if the structure looks incomplete: help me compare the first section of my prompt.txt file with the required JSON output shape
The output shape controls syntax. The next section controls accuracy by grounding each skill in the supplied posting.
- Place your cursor after the final closing brace in prompt.txt.
- Add one blank line.
- Complete the contract by pasting this section:
# Constraints
- Use only information stated in the posting.
- Copy every evidence value exactly from the posting.
- Use only the allowed category and importance values.
- Put any unavoidable inference in assumptions instead of presenting it as fact.
- Keep assumptions as an empty array when no inference is needed.
# Job posting
"""
Junior AI Automation Analyst
We are hiring a Junior AI Automation Analyst to improve internal team workflows.
Required skills:
- Basic Python
- Ability to work with JSON
- Clear written communication
In this role, you will use ChatGPT to draft and improve internal workflows, test outputs against defined acceptance criteria, and document results for non-technical teammates.
Preferred skills:
- Familiarity with Git
- Familiarity with spreadsheets
No prior machine learning research experience is required.
"""
How Do the Constraints Improve Reliability?
- The constraints block unsupported claims from entering the result.
- The evidence rule requires text copied from the source posting.
- The assumptions rule gives uncertain inferences a separate place.
- The embedded posting keeps the source material inside the same request.
- Save prompt.txt.
- Scroll to the bottom of prompt.txt to confirm the file ends with the closing triple quotes.
Prompt Sections in the Wrong Order?
Keep the constraints below the output shape. Keep the complete posting below # Job posting.
Use the full-file reference below to find any missing section: help me identify which section is missing or out of order in my prompt.txt file
Use this check to confirm your complete prompt matches the project contract.
✔️ Awesome, I've got everything!
Your prompt.txt file now contains the complete structured-output contract. Keep it open for the next substep.
ⓧ I'd like to double check the full code
# Context
You are extracting career skills from one job posting. The result will be parsed and checked by a Python program.
# Instructions
Identify only skills that are explicitly supported by the posting. For each skill, provide its name, category, importance, and an exact evidence quote copied from the posting.
# Output format
Return exactly one JSON object. Do not add commentary or Markdown code fences.
Use this shape:
{
"role_title": "string",
"skills": [
{
"name": "string",
"category": "technical | tool | domain | soft",
"importance": "required | preferred",
"evidence": "exact quote from the job posting"
}
],
"assumptions": []
}
# Constraints
- Use only information stated in the posting.
- Copy every evidence value exactly from the posting.
- Use only the allowed category and importance values.
- Put any unavoidable inference in assumptions instead of presenting it as fact.
- Keep assumptions as an empty array when no inference is needed.
# Job posting
"""
Junior AI Automation Analyst
We are hiring a Junior AI Automation Analyst to improve internal team workflows.
Required skills:
- Basic Python
- Ability to work with JSON
- Clear written communication
In this role, you will use ChatGPT to draft and improve internal workflows, test outputs against defined acceptance criteria, and document results for non-technical teammates.
Preferred skills:
- Familiarity with Git
- Familiarity with spreadsheets
No prior machine learning research experience is required.
"""
What Should the Complete Prompt Contain?
The complete file moves from context to instructions. It then defines the output format before applying constraints.
The final section supplies the posting that every extracted skill must use as evidence.
Generate the structured response
The previous request left ChatGPT free to choose prose and bullets. This prompt specifies a machine-readable contract before the model responds.
- Select all text in prompt.txt from # Context through the closing triple quotes.
- Copy the selected prompt.
- Switch back to the ChatGPT conversation from earlier.
- Paste the complete prompt into the message field.
- Submit the message.
You should receive one JSON object containing the role title, a skills array, and an empty assumptions array. The response should contain no introductory or closing commentary.
- Confirm the response begins with an opening brace.
- Confirm the response ends with a closing brace.
- Copy only the JSON object. Leave out any Markdown code-fence markers.
- Switch back to skills.json from the previous step.
- Select the existing prose-and-bullets response.
- Replace the selected response by pasting the JSON object.
- Save skills.json.
ChatGPT output can vary slightly between runs. Use the project sample below to align every field before continuing.
✔️ Awesome, I've got everything!
Your skills.json matches the project sample. You are ready to test whether Python can parse it.
ⓧ I'd like to double check the full code
- Compare every field in your skills.json with the reference below.
- Correct each differing value in skills.json.
- Save skills.json.
{
"role_title": "Junior AI Automation Analyst",
"skills": [
{
"name": "Python",
"category": "technical",
"importance": "required",
"evidence": "Basic Python"
},
{
"name": "JSON",
"category": "technical",
"importance": "required",
"evidence": "Ability to work with JSON"
},
{
"name": "Written communication",
"category": "soft",
"importance": "required",
"evidence": "Clear written communication"
},
{
"name": "ChatGPT",
"category": "tool",
"importance": "required",
"evidence": "use ChatGPT"
},
{
"name": "Workflow improvement",
"category": "domain",
"importance": "required",
"evidence": "draft and improve internal workflows"
},
{
"name": "Output evaluation",
"category": "technical",
"importance": "required",
"evidence": "test outputs against defined acceptance criteria"
},
{
"name": "Git",
"category": "tool",
"importance": "preferred",
"evidence": "Familiarity with Git"
},
{
"name": "Spreadsheets",
"category": "tool",
"importance": "preferred",
"evidence": "Familiarity with spreadsheets"
}
],
"assumptions": []
}
How Is the Structured Data Organized?
Each skill has a name plus a controlled category. Its importance records whether the posting treats it as required or preferred.
The evidence field ties each skill to exact wording from job_posting.txt. The empty assumptions array shows that the result adds no inference.
Prove the file is valid JSON
Python can now test whether the new file follows JSON syntax. A successful parse proves the response is ready for programmatic use.
Before you run the check, do you expect Python to reject the file again or format it successfully?
- Validate and format skills.json by running this command in the integrated terminal:
python3 -m json.tool skills.json
What Does This Result Prove?
The json.tool module parses the file before printing it in an indented format. You should see formatted JSON beginning with the Junior AI Automation Analyst role title.
The command should finish without a parsing error. Your AI response now has syntax that Python can use.
Still Seeing a Parsing Error?
Remove any text outside the opening and closing braces. Code-fence markers or an introductory sentence make the file invalid.
Compare the punctuation in skills.json with the full-file reference. Missing commas or quotation marks can stop the parser.
Ask for help locating the invalid character: help me diagnose why python3 -m json.tool skills.json reports a parsing error
That planned failure is now resolved. Your structured skills data passes Python's syntax check.
Next, you will turn this validated data into a grouped career-skills report.
Build the Skills Analyzer
Your structured JSON now captures the role title plus every skill in predictable fields. That successful parse gives your analyzer reliable input.
Valid JSON is still stored data until Python turns it into a report you can read. In this step, you will load the data and group the skills into a career-focused summary.
In this step, get ready to:
- Create a runnable analyzer that loads skills.json.
- Group the skill names by category.
- Print separate required and preferred skill summaries.
Create the analyzer foundation
The first runnable slice connects the input file to a small report function. Its initial output proves that Python can read the role title from your structured data.
- Click the new-file icon beside ai-career-skills-analyzer in the VS Code file sidebar.
- Type analyze_skills.py as the file name.
- Press Return to create the file.
- Build the first runnable version by pasting this code into analyze_skills.py:
import json
INPUT_FILE = "skills.json"
CATEGORY_ORDER = ["technical", "tool", "domain", "soft"]
def load_json(file_name):
with open(file_name, "r", encoding="utf-8") as file:
return json.load(file)
def print_report(data):
skills = data["skills"]
print("Role: {}".format(data["role_title"]))
print()
def main():
data = load_json(INPUT_FILE)
print_report(data)
if __name__ == "__main__":
main()
What Does This Code Do?
- INPUT_FILE points the analyzer at the structured data in skills.json.
- CATEGORY_ORDER records the order used for the report's category sections.
- load_json() reads the file before converting its JSON content into Python data.
- print_report() starts the report with the role title.
- main() connects the file loader to the report function.
- Save analyze_skills.py with Cmd+S.
- Confirm analyze_skills.py appears beside your existing project files.
- Switch back to the integrated terminal from earlier.
- Run the first analyzer slice with this command:
python3 analyze_skills.py
What Should I See?
The terminal prints Role: Junior AI Automation Analyst. This proves the analyzer opened the correct file and retrieved its role_title value.
That first run is a solid milestone. Your script can now turn a file on disk into the beginning of a career report.
Analyzer Did Not Print the Role?
Confirm that analyze_skills.py sits in the same ai-career-skills-analyzer folder as skills.json.
Check the indentation inside load_json() and main() against the code above.
Help me diagnose why the first analyzer run does not print the role title.
Group the skills by category
Each skill already has a category such as technical or tool. Grouping those names makes the role's skill profile easier to scan.
- Find the blank line directly after print() inside print_report(data).
- Add the category grouping by pasting this code on that blank line:
for category in CATEGORY_ORDER:
names = [
skill["name"]
for skill in skills
if skill["category"] == category
]
if names:
print("{}: {}".format(category.title(), ", ".join(names)))
How Does the Grouping Work?
- The loop follows CATEGORY_ORDER so the report stays consistent between roles.
- The list comprehension collects each matching name value for the current category.
- The final condition skips empty categories so the report only shows useful sections.
- Save analyze_skills.py.
- Print the grouped report by running this command:
python3 analyze_skills.py
What Should the Categories Show?
- Technical contains Python, JSON, plus Output evaluation.
- Tool contains ChatGPT, Git, plus Spreadsheets.
- Domain contains Workflow improvement.
- Soft contains Written communication.
Your analyzer now turns a flat skills array into four readable sections. The role's skill mix is visible at a glance.
Missing a Skill Category?
Check that the new loop remains indented inside print_report(data).
Compare each dictionary key with the existing fields in skills.json.
Help me find why a category or skill is missing from my grouped analyzer report.
Separate required and preferred skills
Categories describe the type of skill. Importance shows whether the posting requires that skill or lists it as preferred.
- Find the line inside print_report(data) that prints category.title().
- Place your cursor on the blank line below the completed category loop.
- Add the importance summaries by pasting this code:
required = [
skill["name"]
for skill in skills
if skill["importance"] == "required"
]
preferred = [
skill["name"]
for skill in skills
if skill["importance"] == "preferred"
]
print()
print("Required skills: {}".format(", ".join(required)))
print("Preferred skills: {}".format(", ".join(preferred) if preferred else "None listed"))
What Does This Code Add?
- required collects skill names whose importance is required.
- preferred collects skill names whose importance is preferred.
- The last two lines print the separate summaries beneath the category sections.
- The preferred summary displays None listed when a future role has no preferred skills.
- Save analyze_skills.py.
Before you run the finished analyzer, which two importance summaries do you expect beneath the category sections?
- Run the completed analyzer with this command:
python3 analyze_skills.py
What Should the Complete Report Show?
The first line shows Role: Junior AI Automation Analyst. Four category lines appear below it.
The final lines begin with Required skills: plus Preferred skills:. Each line contains the matching skill names from skills.json.
You have built the first complete career-skills report. One command now turns structured role data into a summary you can read and compare.
Report Stops Before the Summaries?
Make sure the required plus preferred lists align with the category loop inside print_report(data).
Check that every bracket plus parenthesis in the new section has a matching closing character.
Help me debug why analyze_skills.py stops before printing the required and preferred skill summaries.
✔️ Awesome, I've got everything!
Great work. Save analyze_skills.py so your complete report is ready for the next step.
ⓧ I'd like to double check the full code
import json
INPUT_FILE = "skills.json"
CATEGORY_ORDER = ["technical", "tool", "domain", "soft"]
def load_json(file_name):
with open(file_name, "r", encoding="utf-8") as file:
return json.load(file)
def print_report(data):
skills = data["skills"]
print("Role: {}".format(data["role_title"]))
print()
for category in CATEGORY_ORDER:
names = [
skill["name"]
for skill in skills
if skill["category"] == category
]
if names:
print("{}: {}".format(category.title(), ", ".join(names)))
required = [
skill["name"]
for skill in skills
if skill["importance"] == "required"
]
preferred = [
skill["name"]
for skill in skills
if skill["importance"] == "preferred"
]
print()
print("Required skills: {}".format(", ".join(required)))
print("Preferred skills: {}".format(", ".join(preferred) if preferred else "None listed"))
def main():
data = load_json(INPUT_FILE)
print_report(data)
if __name__ == "__main__":
main()
How to Use This Reference
Compare this full file with your saved analyze_skills.py. Check the indentation plus the order of each function before running the analyzer again.
Your analyzer can now load structured data and present its skills as a readable report. Next, you will add checks that reject unsupported evidence plus invalid labels before the report is trusted.
Validate Evidence and Schema
Your first report already turns structured data into a readable career view. The next question is whether the underlying data deserves to be displayed.
Parseable JSON can still contain invented evidence or unsupported labels. This step adds a validation gate before Python prints the report.
In this step, get ready to:
- Load the source job posting beside the structured skills data.
- Reject invalid fields before they reach the career report.
- Prove the analyzer produces both FAIL and PASS results.
Load the source contract
Evidence checking needs the original posting as its source of truth. Allowed-value sets also give each category or importance field a fixed contract.
- Select analyze_skills.py from the file list in Visual Studio Code.
- Find the constants below import json.
- Expand the constants section by replacing it with this code:
INPUT_FILE = "skills.json"
SOURCE_FILE = "job_posting.txt"
CATEGORY_ORDER = ["technical", "tool", "domain", "soft"]
ALLOWED_CATEGORIES = set(CATEGORY_ORDER)
ALLOWED_IMPORTANCE = {"required", "preferred"}
What Does This Setup Add?
- SOURCE_FILE identifies the posting that supports every evidence quote.
- ALLOWED_CATEGORIES converts the existing category order into a set of accepted labels.
- ALLOWED_IMPORTANCE limits importance values to required or preferred.
- Find the end of load_json().
- Add the source-text loader directly below it by copying this code:
def load_text(file_name):
with open(file_name, "r", encoding="utf-8") as file:
return file.read()
What Does This Function Do?
load_text() reads the complete posting into one string. The validator can then search that string for every evidence quote.
- Save analyze_skills.py.
- Confirm the source setup preserves the current report by running this command in the integrated terminal:
python3 analyze_skills.py
Why Run the Analyzer Now?
You should see the same report beginning with Role: Junior AI Automation Analyst. That complete report proves the new constants plus source reader have not broken the existing flow.
Report No Longer Running?
- Check that SOURCE_FILE appears directly below INPUT_FILE.
- Check that load_text() aligns with load_json() at the left edge.
Help me find the mistake in my new constants or load_text function.
Validate every skill
A schema defines the fields plus value types that your program accepts. Evidence grounding adds a second check against the source posting.
- Find the space between load_text() and print_report().
- Start the validation function in that space by copying this first section:
def validate(data, source_text):
errors = []
if not isinstance(data, dict):
return ["The top-level JSON value must be an object."]
role_title = data.get("role_title")
if not isinstance(role_title, str) or not role_title.strip():
errors.append("role_title must be a non-empty string.")
skills = data.get("skills")
if not isinstance(skills, list) or not skills:
errors.append("skills must be a non-empty list.")
return errors
assumptions = data.get("assumptions")
if not isinstance(assumptions, list):
errors.append("assumptions must be a list.")
What Does the First Section Check?
- errors collects every validation problem for one combined result.
- isinstance() checks the expected container or value type.
- role_title must contain text.
- skills must contain at least one list item.
- assumptions must remain a list.
- Continue validate() directly below the assumptions check by copying this second section:
source_lower = source_text.lower()
for position, skill in enumerate(skills, start=1):
label = "Skill {}".format(position)
if not isinstance(skill, dict):
errors.append("{} must be an object.".format(label))
continue
name = skill.get("name")
category = skill.get("category")
importance = skill.get("importance")
evidence = skill.get("evidence")
if not isinstance(name, str) or not name.strip():
errors.append("{} needs a non-empty name.".format(label))
if category not in ALLOWED_CATEGORIES:
errors.append("{} has an invalid category: {}.".format(label, category))
if importance not in ALLOWED_IMPORTANCE:
errors.append("{} has an invalid importance: {}.".format(label, importance))
if not isinstance(evidence, str) or not evidence.strip():
errors.append("{} needs a non-empty evidence quote.".format(label))
elif evidence.lower() not in source_lower:
errors.append("{} uses evidence not found in the posting: {}.".format(label, evidence))
return errors
How Does Evidence Validation Work?
- source_lower supports case-insensitive evidence matching.
- enumerate() gives each skill a numbered error label.
- ALLOWED_CATEGORIES rejects category labels outside the contract.
- ALLOWED_IMPORTANCE rejects unsupported importance labels.
- evidence.lower() must appear inside the lowercased source posting.
- Save analyze_skills.py.
- Check that Python can parse the complete validator by running this command:
python3 analyze_skills.py
What Does This Check Prove?
The current main() prints the complete career report. Seeing that output proves Python parsed the new validate() function successfully.
Seeing a Syntax or Indentation Problem?
- Check that source_lower starts with four spaces inside validate().
- Check that every field check inside the loop starts with eight spaces.
- Check that return errors aligns with the for statement.
Help me correct the indentation in my validate function.
Test the PASS and FAIL paths
The validation gate only protects the report when main() calls it. The final flow stops before the report whenever a check fails.
- Find the existing main() function near the bottom of analyze_skills.py.
- Replace the complete function with this final version:
def main():
try:
data = load_json(INPUT_FILE)
source_text = load_text(SOURCE_FILE)
except OSError as error:
print("SETUP ERROR: {}".format(error))
return
except ValueError as error:
print("JSON ERROR: {}".format(error))
return
errors = validate(data, source_text)
if errors:
print("FAIL: {} validation issue(s) found.".format(len(errors)))
for error in errors:
print("- {}".format(error))
return
print("PASS: structured output and evidence checks succeeded.")
print_report(data)
How Does the Final Flow Work?
- The try block loads both project files before validation begins.
- OSError handles a missing or inaccessible file.
- ValueError handles malformed JSON.
- errors triggers the FAIL path before print_report() can run.
- PASS appears only after every check succeeds.
- Save analyze_skills.py.
Before you run the completed analyzer, do you expect the unchanged sample data to pass every check?
- Test the completed validation flow by running this command:
python3 analyze_skills.py
What Should the First Run Show?
You should see PASS: structured output and evidence checks succeeded. as the first line. The grouped career report should follow it.
PASS Does Not Appear?
- Check that main() calls validate(data, source_text) before print_report(data).
- Check that skills.json still contains the original sample evidence values.
Help me diagnose why the valid sample data does not produce PASS.
✔️ Awesome, I've got everything!
Your saved analyze_skills.py now loads the source posting before it trusts the structured skills data.
ⓧ I'd like to double check the full code
import json
INPUT_FILE = "skills.json"
SOURCE_FILE = "job_posting.txt"
CATEGORY_ORDER = ["technical", "tool", "domain", "soft"]
ALLOWED_CATEGORIES = set(CATEGORY_ORDER)
ALLOWED_IMPORTANCE = {"required", "preferred"}
def load_json(file_name):
with open(file_name, "r", encoding="utf-8") as file:
return json.load(file)
def load_text(file_name):
with open(file_name, "r", encoding="utf-8") as file:
return file.read()
def validate(data, source_text):
errors = []
if not isinstance(data, dict):
return ["The top-level JSON value must be an object."]
role_title = data.get("role_title")
if not isinstance(role_title, str) or not role_title.strip():
errors.append("role_title must be a non-empty string.")
skills = data.get("skills")
if not isinstance(skills, list) or not skills:
errors.append("skills must be a non-empty list.")
return errors
assumptions = data.get("assumptions")
if not isinstance(assumptions, list):
errors.append("assumptions must be a list.")
source_lower = source_text.lower()
for position, skill in enumerate(skills, start=1):
label = "Skill {}".format(position)
if not isinstance(skill, dict):
errors.append("{} must be an object.".format(label))
continue
name = skill.get("name")
category = skill.get("category")
importance = skill.get("importance")
evidence = skill.get("evidence")
if not isinstance(name, str) or not name.strip():
errors.append("{} needs a non-empty name.".format(label))
if category not in ALLOWED_CATEGORIES:
errors.append("{} has an invalid category: {}.".format(label, category))
if importance not in ALLOWED_IMPORTANCE:
errors.append("{} has an invalid importance: {}.".format(label, importance))
if not isinstance(evidence, str) or not evidence.strip():
errors.append("{} needs a non-empty evidence quote.".format(label))
elif evidence.lower() not in source_lower:
errors.append("{} uses evidence not found in the posting: {}.".format(label, evidence))
return errors
def print_report(data):
skills = data["skills"]
print("Role: {}".format(data["role_title"]))
print()
for category in CATEGORY_ORDER:
names = [
skill["name"]
for skill in skills
if skill["category"] == category
]
if names:
print("{}: {}".format(category.title(), ", ".join(names)))
required = [
skill["name"]
for skill in skills
if skill["importance"] == "required"
]
preferred = [
skill["name"]
for skill in skills
if skill["importance"] == "preferred"
]
print()
print("Required skills: {}".format(", ".join(required)))
print("Preferred skills: {}".format(", ".join(preferred) if preferred else "None listed"))
def main():
try:
data = load_json(INPUT_FILE)
source_text = load_text(SOURCE_FILE)
except OSError as error:
print("SETUP ERROR: {}".format(error))
return
except ValueError as error:
print("JSON ERROR: {}".format(error))
return
errors = validate(data, source_text)
if errors:
print("FAIL: {} validation issue(s) found.".format(len(errors)))
for error in errors:
print("- {}".format(error))
return
print("PASS: structured output and evidence checks succeeded.")
print_report(data)
if __name__ == "__main__":
main()
What Should You Compare?
Check each constant plus function against your saved file. Pay close attention to indentation inside validate() and main().
The valid sample proves the happy path. A deliberate unsupported quote now tests whether the gate can stop believable-looking data.
- Switch to skills.json in the VS Code file list.
- Find the first evidence value containing Basic Python.
- Replace Basic Python with invented evidence.
- Save skills.json.
Before you run the analyzer, should an evidence quote that is absent from the posting reach the report?
- Challenge the validation gate by running this command:
python3 analyze_skills.py
Why Is FAIL the Correct Result?
You should see a first line beginning with FAIL: 1 validation issue(s) found. The next line identifies invented evidence as missing from the posting.
The report does not print because the unsupported claim failed the evidence gate. This is the intended result.
Career Report Still Printing?
- Confirm that the first evidence value in skills.json is exactly invented evidence.
- Confirm that the error branch in main() returns before print_report(data).
Help me find why unsupported evidence still reaches my report.
- Replace invented evidence with Basic Python.
- Save the restored skills.json file.
Before the final run, what should happen now that every evidence quote appears in the source posting?
- Complete the end-to-end verification by running this command:
python3 analyze_skills.py
What Does the Final Run Prove?
The first line should be PASS: structured output and evidence checks succeeded. You should then see the role title plus grouped category lines.
The required plus preferred skill lines should appear at the bottom. The restored data has passed the complete contract.
Still Seeing FAIL?
- Check that the restored evidence value is exactly Basic Python.
- Check that every other evidence value in skills.json still matches text in job_posting.txt.
- Save both files before running the analyzer again.
Help me identify which skill is preventing the final PASS result.
That is the trust boundary working. Your analyzer now displays career data only after its structure plus evidence have earned a PASS result.
Secret mission
Compare Two AI Roles
One job posting captures one employer's priorities. Compare a second role with the same structured contract to reveal the skills that recur across both opportunities.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep your local project files, pause your work, or delete the folder. This project created no paid services or cloud resources, so keeping the folder has no ongoing cost.
Resources you used:
- Local project folder ai-career-skills-analyzer.
- Source postings in job_posting.txt and job_posting_second.txt.
- Reusable extraction prompt in prompt.txt.
- Structured role data in skills.json and skills_second.json.
- Python scripts in analyze_skills.py and compare_roles.py.
Keep everything running
Keeping the folder preserves your analyzer for future role checks. No project process stays running in the background.
- Keep ai-career-skills-analyzer in its current location.
- Reuse prompt.txt when you investigate another role.
- Reuse analyze_skills.py to validate another structured response.
- Reuse compare_roles.py to compare another pair of roles.
Pause - I'll come back to this later
A pause closes your active tools while preserving every project file. You can return to the same folder whenever you are ready.
- Save each open project file in VS Code.
- Close VS Code.
- Close the ChatGPT window or browser tab used for this project.
- Keep the ai-career-skills-analyzer folder in its current location.
Delete - I don't want to use this again
Deleting the folder can feel drastic. Your installed tools and ChatGPT account stay available.
- Close VS Code.
- Press Cmd+Space to open macOS search.
- Type Finder into the search field.
- Press Enter to open Finder.
- Search for ai-career-skills-analyzer in Finder.
- Move the ai-career-skills-analyzer folder to Trash.
- Confirm the ai-career-skills-analyzer folder appears in Trash.
No remote cleanup is required because the project created no cloud resources or API keys.
Nice Work!
Nice Work!
You reached the finish line! Your AI Career Skills Analyzer now uses ChatGPT, JSON, and Python to turn job postings into checked career reports.
You've learned how to:
- Design a reusable structured prompt that gives ChatGPT clear context, constraints, evidence requirements, and an exact output shape.
- Prove why unstructured output breaks automation through a deliberate JSON parsing failure.
- Build a validated skills analyzer that rejects malformed data or unsupported evidence before grouping role skills.
- (Secret Mission) Reuse the workflow for a cross-role comparison that reveals shared skills to prioritize in your learning plan.
You now have a reusable workflow for checking AI output before using it to guide your career learning.
Ready to quiz yourself?