Guides
Common tasks, real-world patterns, copy-paste friendly.
1. Managing multiple job descriptions
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)
JobDescription
instance. It is only computed on first access to jd.parsed. Reusing the
same object across many resumes means one LLM call for the JD instead of N.
2. Free-text JDs and resumes
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)
3. Web upload handlers (bytes in, result out)
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()
4. Folder analysis: recursion, filters, and parallelism
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.
max_workers too aggressively. Start with 4 and increase only if your
provider quota is generous.
5. Downloading PDF reports
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")
6. Inspecting the result
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()
7. Filtering and ranking in pure Python
# 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 [])]
8. Handling provider errors
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 the API reference.