HR-ATS-Portal/backend/summary_gate/plugins.py

81 lines
2.6 KiB
Python

"""Summary-gate configuration and the fail policy.
Pure module: no FastAPI imports and no HTTPException.
Non-DB config is module-level load_dotenv() + os.getenv (house style). Model /
token / effort / cache knobs come from the bulk-ats Settings, same as
inbox_classifier.plugins.get_triage_settings.
"""
from __future__ import annotations
import os
from dotenv import load_dotenv
from summary_gate.prompt import PROMPT_VERSION
from app.core.config import Settings, get_settings
load_dotenv()
def _flag(name, default) -> bool:
raw=(os.getenv(name) or "").strip().lower()
if not raw:
return default
return raw in ("1","true","yes","on")
# false restores pre-gate behaviour: every pair with a CV is ATS-scored.
GATE_ENABLED=_flag("SUMMARY_GATE_ENABLED",True)
# true: a provider outage or a missing key lets the pair through to ATS.
# Fail-closed would silently skip scoring when OPENAI_API_KEY is unset.
GATE_FAIL_OPEN=_flag("SUMMARY_GATE_FAIL_OPEN",True)
GATE_MAX_SUMMARY_CHARS=max(int(os.getenv("SUMMARY_GATE_MAX_SUMMARY_CHARS") or 500),1)
# Same bound as the inbox intake body cap — enough for title + requirements.
GATE_MAX_JD_CHARS=max(int(os.getenv("SUMMARY_GATE_MAX_JD_CHARS") or 4000),1)
# 0 disables the uncertainty branch (0.0 < 0.0 is False).
GATE_MIN_CONFIDENCE=float(os.getenv("SUMMARY_GATE_MIN_CONFIDENCE") or 0)
PROMPT_CACHE_KEY=f"summary-gate-{PROMPT_VERSION}"
UNCLASSIFIED_PREFIX="unclassified:"
CLASSIFIED="classified"
LOW_CONFIDENCE="low_confidence"
ERROR="error"
# Backend-local skip code. Not an app.core.errors.ErrorCode — the bulk-ats
# engine never sees this pair; the CV is never opened.
SUMMARY_NOT_SUITABLE="SUMMARY_NOT_SUITABLE"
def get_gate_settings() -> Settings:
return get_settings()
def clip_text(value, limit) -> str:
text=str(value or "")
if len(text)<=limit:
return text
return text[:limit]
def should_score(verdict, error_code="") -> tuple[bool,str,str]:
"""(proceed, status, reason) — the entire fail policy, in one place.
No verdict means the model could not be consulted. SUMMARY_GATE_FAIL_OPEN
decides. A present verdict uses is_suitable, unless confidence is below
SUMMARY_GATE_MIN_CONFIDENCE (the gradient threshold).
"""
if verdict is None:
reason=f"{UNCLASSIFIED_PREFIX}{error_code or 'unknown'}"[:60]
return GATE_FAIL_OPEN,ERROR,reason
if verdict.confidence<GATE_MIN_CONFIDENCE:
return GATE_FAIL_OPEN,LOW_CONFIDENCE,SUMMARY_NOT_SUITABLE
if verdict.is_suitable:
return True,CLASSIFIED,"suitable"
return False,CLASSIFIED,SUMMARY_NOT_SUITABLE