-
Notifications
You must be signed in to change notification settings - Fork 0
Guides
Common tasks, real-world patterns, copy-paste friendly.
A JobDescription is an ordinary Python object. You can keep a dictionary of them, one per open role, and run any resume against any JD.
roles = {
"senior_python": reveilio.JobDescription.from_file("roles/senior_python.pdf"),
"ml_engineer": reveilio.JobDescription.from_file("roles/ml_engineer.pdf"),
"devrel": reveilio.JobDescription.from_text(pasted_devrel_text),
}
# Score Alice against every open role to find her best fit
alice = reveilio.Resume.from_file("resumes/alice.pdf")
fits = {name: reveilio.analyze_resume(alice, jd) for name, jd in roles.items()}
best = max(fits.items(), key=lambda kv: kv[1].overall_score)
print("Alice best fits", best[0], "at", best[1].overall_score)💡 Tip: the structured JD parse is cached on the
JobDescriptioninstance. It is only computed on first access tojd.parsed. Reusing the same object across many resumes means one LLM call for the JD instead of N.
Sometimes the source of truth is not a file. It is a row in your ATS or a message in Slack. Both JobDescription and Resume accept raw strings:
jd = reveilio.JobDescription.from_text(row["job_description"])
resume = reveilio.Resume.from_text(row["resume_text"], filename=row["candidate_id"])
result = reveilio.analyze_resume(resume, jd)When wiring reveilio into a Flask, FastAPI, or Django view, you typically receive bytes from an uploaded file. Use from_bytes:
@app.post("/analyze")
def analyze(jd_file, resume_file):
jd = reveilio.JobDescription.from_bytes(jd_file.read(), filename=jd_file.filename)
rs = reveilio.Resume.from_bytes(resume_file.read(), filename=resume_file.filename)
return reveilio.analyze_resume(rs, jd).model_dump()results = reveilio.analyze_folder(
"./resumes",
jd,
extensions=(".pdf", ".docx"), # limit to just these two types
max_workers=8, # LLM calls are I/O-bound, so threads help
recursive=True, # walk into subfolders
)Results are sorted by overall_score descending, with rank assigned from 1 to N. Ties preserve the LLM-returned order.
⚠️ Rate limits: hosted LLMs will return HTTP 429 if you setmax_workerstoo aggressively. Start with 4 and increase only if your provider quota is generous.
Two exporters ship with the package. Both write a file to disk and return the resulting pathlib.Path:
# Per-candidate detailed report
reveilio.save_report_pdf(result, "reports/alice.pdf")
# Batch ranking summary
reveilio.save_batch_report_pdf(results, "reports/ranking.pdf")For the raw bytes (for example, to stream a PDF in an HTTP response), bypass the convenience wrapper and call the underlying builder:
from reveilio.reports.pdf import generate_candidate_report
buf = generate_candidate_report(result.model_dump())
return Response(buf.getvalue(), media_type="application/pdf")An AnalysisResult is a pydantic model. Every field is documented and ready to use:
result.overall_score # float, 0-100
result.confidence_level # 'High' / 'Medium' / 'Low'
result.recommendation # 'Shortlist' / 'Needs Review' / 'Not Suitable'
result.detailed_scores # dict: dimension -> {score, reasoning}
result.strengths # list[str]
result.weaknesses # list[str]
result.career_flags # list[str]
result.ai_summary # executive critique (about 120 words)
result.suggested_questions # populated only when recommendation == 'Shortlist'
result.candidate_data.name # extracted structured fields
result.candidate_data.skills
result.candidate_data.experience # list[ExperienceItem]
result.rank # only set by analyze_folder()# Only shortlisted candidates, sorted by experience
shortlist = [r for r in results if r.recommendation == "Shortlist"]
shortlist.sort(key=lambda r: r.candidate_data.total_experience, reverse=True)
# Candidates with a specific skill
python_devs = [r for r in results
if "Python" in (r.candidate_data.skills or [])]The scorer wraps its LLM call in a try / except and returns a safe fallback result rather than raising, so a single bad resume will not crash a 500-resume batch. You can detect the error case by checking the summary:
if "Error" in (result.ai_summary or ""):
print("LLM call failed for", result.candidate_data.filename)For the parsing and configuration layers (missing API key, unsupported file extension), reveilio does raise. Those are programmer errors worth stopping for.
Continue to API reference.
v0.1.1 · docs
Getting started
Core features
- Configuration & providers
- Job descriptions
- Resume parsing
- Scoring & analysis
- PDF reports
- Database storage
Using reveilio
Deployment
Deep dive