Job descriptions

How reveilio loads, parses, and structures a job description.

The JobDescription class

A JobDescription holds the raw text of a JD and a lazily-computed structured parse. The LLM is only called the first time you access .parsed. After that, the result is cached on the object.

Each instance is fully independent. You can create multiple JobDescription objects for different open roles and reuse them across any number of resumes.

Three ways to create one

1. From a file on disk

Supports .pdf, .docx, .doc, and .txt files.

jd = reveilio.JobDescription.from_file("roles/senior_python.pdf")

Reveilio extracts the plain text from the file, stores it in jd.text, and sets jd.source to the file path.

2. From free text

Paste a JD directly from an email, database row, or API response.

jd = reveilio.JobDescription.from_text("""
Senior Python Engineer, 5+ years experience.
Must know FastAPI, PostgreSQL, and AWS.
Experience with CI/CD pipelines required.
""")

3. From in-memory bytes

Useful when integrating with web frameworks where uploaded files arrive as bytes.

jd = reveilio.JobDescription.from_bytes(
    uploaded_file.read(),
    filename="senior_python.pdf",  # extension determines the parser
)

What gets extracted

When you access jd.parsed, the LLM extracts a structured dictionary containing:

FieldDescription
position_titleThe role title (e.g. "Senior Full Stack Developer").
companyThe hiring company name.
locationWork location.
work_modeRemote, hybrid, or on-site.
required_skillsList of must-have technical skills.
preferred_skillsList of nice-to-have skills.
soft_skillsCommunication, leadership, etc.
required_experienceMinimum years of experience.
preferred_experiencePreferred years of experience.
education_requirementsList of required degrees.
certificationsRequired or preferred certifications.
tools_and_technologiesSpecific tools mentioned.
programming_languagesLanguages required.
frameworksFrameworks required (React, Django, etc.).
databasesDatabase systems mentioned.
cloud_platformsAWS, Azure, GCP, etc.
methodologiesAgile, Scrum, etc.
responsibilitiesList of job responsibilities.

Lazy parsing and caching

The structured parse is computed only on first access to jd.parsed. This means creating a JobDescription object is instant. The LLM call only happens when you actually need the parsed data, which is typically when you call analyze_resume().

Once computed, the result is cached. If you score 500 resumes against the same JD, the JD parsing LLM call happens exactly once.

jd = reveilio.JobDescription.from_file("jd.pdf")  # no LLM call yet
print(jd.parsed["position_title"])               # LLM call happens here
print(jd.parsed["required_skills"])              # cached, no second call

Managing multiple JDs

Each JobDescription is an independent object with its own cache. Keep a dictionary of them for multi-role screening:

roles = {
    "backend": reveilio.JobDescription.from_file("roles/backend.pdf"),
    "frontend": reveilio.JobDescription.from_file("roles/frontend.txt"),
    "devops": reveilio.JobDescription.from_text(devops_jd_text),
}

# Score one candidate against all open roles
for role_name, jd in roles.items():
    result = reveilio.analyze_resume("resumes/alice.pdf", jd)
    print(f"{role_name}: {result.overall_score}%")

Inspecting the raw text

The extracted text is always available via jd.text. This is useful for debugging or for feeding into your own processing pipeline.

print(jd.text[:500])     # first 500 chars of extracted text
print(jd.source)         # file path or "text"
Note: if the LLM cannot parse the JD (e.g. the text is too garbled), jd.parsed returns a safe fallback dictionary with "position_title": "N/A" and empty lists for skills and requirements.

Continue to Resume parsing.