78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
"""Summary-gate entrypoint — one Responses call per (summary, job) pair.
|
|
|
|
Pure module: no FastAPI imports and no HTTPException.
|
|
Never raises: a provider outage is a policy decision (SUMMARY_GATE_FAIL_OPEN).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from summary_gate.agent_setup import get_gate
|
|
from summary_gate.plugins import (
|
|
GATE_ENABLED,
|
|
GATE_MAX_JD_CHARS,
|
|
GATE_MAX_SUMMARY_CHARS,
|
|
clip_text,
|
|
should_score,
|
|
)
|
|
|
|
from app.core.errors import ATSError, classify_error
|
|
|
|
logger=logging.getLogger("summary.gate")
|
|
|
|
EMPTY_INPUT="empty_input"
|
|
_allow_cache: dict[tuple[str,str],bool]={}
|
|
|
|
|
|
async def classify_summary(summary, job_description) -> tuple:
|
|
"""Judge one summary against one JD. Never raises.
|
|
|
|
(verdict, "") on success; (None, error_code) when the model could not be
|
|
consulted or returned something unusable.
|
|
"""
|
|
text=clip_text(summary,GATE_MAX_SUMMARY_CHARS).strip()
|
|
jd=clip_text(job_description,GATE_MAX_JD_CHARS).strip()
|
|
if not text or not jd:
|
|
return None,EMPTY_INPUT
|
|
|
|
try:
|
|
gate=get_gate()
|
|
verdict=await gate.classify(jd,text)
|
|
logger.info(
|
|
"summary gate: suitable=%s confidence=%s",
|
|
verdict.is_suitable,
|
|
verdict.confidence,
|
|
)
|
|
return verdict,""
|
|
except ATSError as e:
|
|
logger.warning("summary gate failed: code=%s",e.error_code)
|
|
return None,e.error_code
|
|
except Exception as e:
|
|
code,_=classify_error(e)
|
|
logger.warning("summary gate failed: code=%s exc=%s",code,type(e).__name__)
|
|
return None,code
|
|
|
|
|
|
async def allow_ats(summary, job_description) -> bool:
|
|
"""True when full ATS should run.
|
|
|
|
No summary, or the gate disabled: pass through so the first score can
|
|
write professional_summary. A present summary is the gradient: only a
|
|
suitable verdict (above SUMMARY_GATE_MIN_CONFIDENCE) continues.
|
|
"""
|
|
if not GATE_ENABLED:
|
|
return True
|
|
text=(summary or "").strip()
|
|
if not text:
|
|
return True
|
|
jd=(job_description or "").strip()
|
|
cache_key=(text,jd)
|
|
cached=_allow_cache.get(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
verdict,error=await classify_summary(text,jd)
|
|
proceed,_,_=should_score(verdict,error)
|
|
_allow_cache[cache_key]=proceed
|
|
return proceed
|