ResumeCustomizer / utils.py
djsnuggz's picture
Use Claude Sonnet 3.7
cc21f28 verified
import json
from anthropic import Anthropic
import os
def process_ksas(job_description):
ksas = _ksas(job_description)
job_duties, req_quals, pref_quals = ksas['job_duties'], ksas['required_qualifications'], ksas['preferred_qualifications']
ksas_str = 'Job Duties\n' + '\n'.join(job_duties) + '\nRequired Qualifications\n' + '\n'.join(req_quals) + '\nPreferred Qualifications\n' + '\n'.join(pref_quals)
return ksas_str
def _ksas(job_description):
prompt = _ksa_extraction_prompt(job_description)
system = "You are a job description analyzer. Return only JSON in the format: {\"job_duties\": [\"duty 1\", \"duty 2\"], \"required_qualifications\": [\"qual 1\", \"qual 2\"], \"preferred_qualifications\": [\"pref 1\", \"pref 2\"]}"
return call_claude(prompt=prompt, system=system)
def _ksa_extraction_prompt(job_description):
return f"""Extract Key Selection Areas (KSAs) from this job description, maintaining exact original wording. Organize them into these categories:
Job Duties:
- List duties/responsibilities as stated in description
- Include any key activities or deliverables mentioned
Required Qualifications:
- List all explicitly stated required qualifications
- Include any specific metrics (e.g., years of experience)
- Include required technical skills, expertise, or knowledge areas
Preferred Qualifications:
- List only qualifications explicitly marked as "preferred," "desired," or similar
- Maintain any specific metrics mentioned
Do not list any implied requirements.
Format the response as JSON:
{{
"job_duties": ["duty 1", "duty 2", ...],
"required_qualifications": ["qual 1", "qual 2", ...],
"preferred_qualifications": ["pref 1", "pref 2", ...]
}}
Job Description:
{job_description}"""
def process_analysis(resume_data, job_description, ksas, freeform_context):
raw_analysis_output = _analysis(resume_data, job_description, ksas, freeform_context)
output = ["Analysis:"]
for a in raw_analysis_output['analysis']:
output.append("- " + a)
output.append("Questions:")
for q in raw_analysis_output['questions']:
output.append("- " + q)
output.append("Recommendation: " + raw_analysis_output['recommendation']['decision'])
output.append("Rationale: " + raw_analysis_output['recommendation']['rationale'])
return("\n".join(output))
def _analysis(resume_data, job_description, ksas, freeform_context):
prompt = _analysis_prompt(resume_data, job_description, ksas, freeform_context)
system = """You are an expert resume consultant analyzing candidate fit. Return only JSON in the format: {
"analysis": ["key insight 1", "key insight 2", ...],
"questions": ["question 1", "question 2", ...],
"recommendation": {
"decision": "Go/No Go",
"rationale": "Brief explanation"
}
}"""
return call_claude(prompt=prompt, system=system)
def _analysis_prompt(resume_data, job_description, ksas, freeform_context):
return f"""You are an expert resume consultant analyzing a candidate's background for a specific role.
Target Job Description:
{job_description}
Core Requirements (for reference):
{ksas}
Candidate's Resume Data:
{resume_data}
Additional Context and Background:
{freeform_context}
Task:
Review all materials and provide two things:
1. Key insights about how the candidate's experience aligns with the role
2. If applicable, clarifying questions that would help optimize the candidate's application with respect to the core requirements
3. A clear "Go/No Go" recommendation with brief justification
Requirements for Analysis:
- Identify strongest alignment points between experience and job requirements
- Note any potential gaps or areas needing clarification
- Consider both explicit and implicit job requirements
- Focus on relevant experience only
- Do not make assumptions about undocumented experience
Requirements for Questions:
- Ask for specific details that would strengthen the application
- Focus questions on areas where additional context could help highlight relevant experience
- Ask about any ambiguous experiences that might be valuable if clarified
- Only ask questions that would materially improve the application if answered
- Prioritize questions about experiences mentioned in the materials, not hypothetical experience
- Questions should be relevant to the core requirements
Requirements for Recommendation:
- Provide a clear "Go" or "No Go" recommendation
- Base recommendation on alignment between requirements and documented experience
- Consider both minimum and preferred qualifications
- Include 1-2 sentences explaining the recommendation
- Be direct and honest while remaining constructive
Important:
- Only reference information provided in the materials
- Questions should be specific and actionable
- Don't feel like you always need to ask a specific number of questions if you have all of the information you need
- Focus on details that would be appropriate to include in a resume or cover letter
- Consider the role's specific requirements when forming questions
"""
def process_core_content(resume_data, job_description, ksas, freeform_context):
experience_bullets = process_job_experience(resume_data, job_description, ksas, freeform_context)
technical_skills = process_technical_skills(resume_data, job_description, ksas)
return experience_bullets, technical_skills
def process_job_experience(resume_data, job_description, ksas, freeform_context):
output = []
# Process each job experience
for job in resume_data['experience']:
output.append(f"\nOptimizing bullets for: {job['title']}")
optimized = optimize_job_experience(job, job_description, ksas, freeform_context)
output.append("\nOptimized bullets:")
for bullet in optimized['bullets']:
output.append(f"• {bullet}")
return "\n".join(output)
def optimize_job_experience(job_experience, job_description, ksas, freeform_context):
prompt = _experience_optimization_prompt(job_experience, job_description, ksas, freeform_context)
system = "You are an expert resume writer. Return ONLY valid JSON with no explanation, formatting, or additional text. The response must exactly match this format: {\"bullets\": [\"bullet1\", \"bullet2\", ...]}"
return call_claude(prompt=prompt, system=system)
def _experience_optimization_prompt(job_experience, job_description, ksas, freeform_context):
return f"""Given this job experience and target job description, create optimized bullet points that best position the candidate for the role. Choose the number of bullets based on the relevance and importance of this experience to the target role.
Historical Job Title: {job_experience['title']}
Complete Job Description:
{job_description}
Core Requirements (use these as your primary reference for optimization):
{ksas}
Available bullet points and variations:
{_formatted_bullets(job_experience['bullets'])}
Additional Context (use to enhance understanding of experiences):
{freeform_context}
Important:
- Read the complete job description for full context
- When prioritizing content, focus primarily on the Core Requirements listed above
- Use the Additional Context to better understand experience details. You can use the information from this section as necessary, but ensure use the experience bullets as the primary source of information
- If there's ambiguity about an experience, refer to the Additional Context for clarification
- Use strong action verbs
- Quantify impact where possible
- Highlight relevant technical skills
- Focus on achievements over responsibilities
- Maintain truthfulness - only use information present in the variations
- The reader should not be able to tell AI wrote the bullet points
"""
def _formatted_bullets(bullets):
formatted = []
for bullet in bullets:
formatted.append(f"Headline: {bullet['headline']}")
formatted.append("Variations:")
for variation in bullet['variations']:
formatted.append(f"- {variation}")
formatted.append("") # Empty line between bullet groups
return "\n".join(formatted)
def process_technical_skills(resume_data, job_description, ksas):
output = []
raw_tech_skills = resume_data['technical_skills']
optimized_tech_skills = optimize_technical_skills(raw_tech_skills, job_description, ksas)
output.append("Technical Skills:\n\n")
for skill_cat in optimized_tech_skills['technical_skills']:
output.append(skill_cat['category'] + ": " + ', '.join(skill_cat['skills']))
return "\n".join(output)
def optimize_technical_skills(tech_skills, job_description, ksas):
prompt = _skills_prompt(tech_skills, job_description, ksas)
system = "You are an expert resume writer. Return ONLY valid JSON with no explanation or additional text. The response must exactly match this format: {\"technical_skills\": [{\"category\": \"Category Name\", \"skills\": [\"Skill 1\", \"Skill 2\"]}, ...]}"
return call_claude(prompt=prompt, system=system)
def _skills_prompt(all_skills, job_description, ksas):
return f"""You are an expert resume writer helping optimize a technical skills section for a specific role.
Target Job Description:
{job_description}
Core Requirements (use these as your primary reference for optimization):
{ksas}
Available skills and their current categories:
{_formatted_skills(all_skills)}
Task:
Create an optimized technical skills section that best positions the candidate for this role.
Requirements:
1. Read the complete job description for context
2. When selecting and ordering skills, prioritize those that directly match the Core Requirements
3. If there's ambiguity about skill importance, defer to the Core Requirements
4. Order categories from most to least relevant for this specific role
5. Within each category, order skills from most to least relevant
6. Create new categories or rename existing ones if it would better align with the job requirements
7. Include only relevant skills; omit those that don't add value for this role
8. Keep categories and skills lists concise and impactful
9. Use standard industry terminology for categories
Format the response as a JSON list:
{{"technical_skills": [
{{"category": "Category Name", "skills": ["Skill 1", "Skill 2", ...]}},
...
]}}
Important:
- Don't assume skill importance based on frequency of appearance
- Only include skills that are present in the input list
- Focus on skills that align with job requirements
- Ensure category names reflect current industry standards
"""
def _formatted_skills(tech_skills):
cat_str_out = [f"{cat['category']}: " + ', '.join(cat['skills']) for cat in tech_skills]
return("\n".join(cat_str_out))
def process_summary(job_description, ksas, experience_bullets, technical_skills, resume_context, summary_sample):
summary = _summary(job_description, ksas, experience_bullets, technical_skills, resume_context, summary_sample)
summary_str = 'Summary\n' + '\n'.join(summary['summary'])
return summary_str
def _summary(job_description, ksas, experience_bullets, technical_skills, resume_context, summary_sample):
prompt = _summary_prompt(job_description, ksas, experience_bullets, technical_skills, resume_context, summary_sample)
system = "You are an expert resume writer. Return only JSON in the format: {\"summary\": [\"bullet1\", \"bullet2\", ...]}"
return call_claude(prompt=prompt, system=system)
def _summary_prompt(job_description, ksas, experience_bullets, technical_skills, resume_context, summary_sample):
return f"""You are an expert resume writer creating a powerful summary section for a specific role.
Target Job Description:
{job_description}
Core Requirements (use as primary reference):
{ksas}
Candidate's Optimized Experience:
{experience_bullets}
Candidate's Optimized Technical Skills:
{technical_skills}
Additional Context:
{resume_context}
Writing Style Reference:
{summary_sample}
Task:
Create 5-10 impactful summary bullets that will immediately convince the hiring manager to carefully review this resume.
Requirements:
1. Focus heavily on matching the Core Requirements, especially required qualifications
2. Use only information provided in the experience bullets, technical skills, and resume context
3. Do not fabricate or embellish experience
4. Order bullets from most to least relevant to the role
5. Keep total length under 100 words
6. Match the style of the provided summary sample while prioritizing content alignment with job requirements
7. Write for quick scanning - each bullet should build confidence in the candidate's fit
8. If a job requirement isn't addressed, assume the candidate lacks that experience
Format:
Return 5-10 bullets in JSON format: {{"summary": ["bullet1", "bullet2", ...]}}
Important:
- Prioritize demonstrating fit for core job requirements
- Use strong, active language
- Be specific and quantifiable where possible
- Focus on achievements and capabilities
- Maintain truthfulness - only use provided information
- The reader should not be able to tell AI wrote the summary
"""
def _cover_letter_prompt(job_description, ksas, experience_bullets, technical_skills, resume_context, summary, cover_letter_sample):
return f"""You are writing a compelling cover letter for a specific role, matching the candidate's authentic writing style.
Target Job Description:
{job_description}
Core Requirements (for reference):
{ksas}
Candidate's Qualifications and Experience:
Summary:
{summary}
Experience Details:
{experience_bullets}
Technical Skills:
{technical_skills}
Additional Background:
{resume_context}
Writing Style Reference (match this tone and structure):
{cover_letter_sample}
Task:
Write a one-page cover letter that demonstrates the candidate is an excellent fit for this role.
Requirements:
1. Match the candidate's writing style exactly - natural, professional, and personally engaging
2. Focus on experiences that directly address the job's core requirements
3. Use only information provided in the resume materials.
4. Highlight achievements that differentiate the candidate
5. Keep to one page in length
6. Maintain first-person perspective throughout
Important:
- Do not fabricate or embellish any experiences
- If a requirement isn't addressed in the provided materials, do not mention it
- Focus on specific, concrete examples rather than generic statements
- Match the personal, authentic tone of the sample letter
- Match the general structure of the sample letter
- The reader should not be able to tell AI wrote the cover letter
Format the response as JSON: {{"cover_letter": "Dear Hiring Manager,\\n\\n[cover letter content]\\n\\nSincerely,"}}"""
def _cover_letter(job_description, ksas, experience_bullets, technical_skills, resume_context, summary, cover_letter_sample):
prompt = _cover_letter_prompt(job_description, ksas, experience_bullets, technical_skills, resume_context, summary, cover_letter_sample)
system = "You are an expert resume writer crafting cover letters. Return ONLY valid JSON with no explanation or additional text. The response must exactly match this format: {\"cover_letter\": \"Dear Hiring Manager,\\n\\n[letter content]\\n\\nSincerely,\"} Maintain natural writing style and first-person perspective."
return call_claude(prompt=prompt, system=system)['cover_letter']
def call_claude(prompt, system, api_key=None):
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
raise ValueError(
"No API key found. Please add your Anthropic API key as a secret named 'ANTHROPIC_API_KEY' in your Space's settings."
)
client = Anthropic(api_key=api_key)
response = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=1500,
temperature=0,
system=system,
messages=[{
"role": "user",
"content": prompt
}]
)
try:
result = json.loads(response.content[0].text)
return result
except Exception as e:
print("Raw response:", response.content[0].text) # Debug line
raise ValueError(f"Failed to parse Claude's response: {e}")