HR-ATS-Portal/backend/employment_agent/decorators.py

79 lines
2.7 KiB
Python

"""Employment response decorators for `parse_employment_response`.
Pure module: no FastAPI imports, no HTTPException, and no module-level state.
Mirrors job/candidate/decorators.py — stacked wrappers that clean LLM output
before the task persists it:
raw JSON -> require_json_object -> clamp_company_to_resume
-> clamp_education_to_resume -> parse_employment_response
"""
from __future__ import annotations
from functools import wraps
from employment_agent.prompt import EDUCATION,NO_COMPANY
def require_json_object(func):
"""Reject non-dict LLM payloads before field parsing runs."""
@wraps(func)
def wrapper(data,resume_text="",*args,**kwargs):
if not isinstance(data,dict):
raise RuntimeError(f"model did not return a JSON object: {data!r}")
return func(data,resume_text,*args,**kwargs)
return wrapper
def clamp_company_to_resume(func):
"""Keep company only when it appears in resume_text; else NO_COMPANY."""
@wraps(func)
def wrapper(data,resume_text="",*args,**kwargs):
company,education,current_title=func(data,resume_text,*args,**kwargs)
company=(company or "").strip()
if not company or company.lower()==NO_COMPANY.lower():
return NO_COMPANY,education,current_title
haystack=(resume_text or "").lower()
if company.lower() not in haystack:
return NO_COMPANY,education,current_title
return company,education,current_title
return wrapper
def clamp_education_to_resume(func):
"""Keep education only when it appears in resume_text; else EDUCATION."""
@wraps(func)
def wrapper(data,resume_text="",*args,**kwargs):
company,education,current_title=func(data,resume_text,*args,**kwargs)
education=(education or "").strip()
if not education or education.lower()==EDUCATION.lower():
return company,EDUCATION,current_title
haystack=(resume_text or "").lower()
if education.lower() not in haystack:
return company,EDUCATION,current_title
return company,education,current_title
return wrapper
@require_json_object
@clamp_company_to_resume
@clamp_education_to_resume
def parse_employment_response(data,resume_text:str="") -> tuple[str,str]:
"""Pull company + education from LLM JSON; decorators clamp to the resume."""
current=data.get("current_employment")
education=data.get("education")
current_title=data.get("current_title")
if not isinstance(current,str):
current=""
if not isinstance(education,str):
education=""
if not isinstance(current_title,str):
current_title=""
return current.strip(),education.strip(),current_title.strip()