81 lines
2.6 KiB
Python
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
|