"""Intake-gate entrypoint — one Responses call per email. Pure module: no FastAPI imports and no HTTPException. Called from inbox.views.Email; no HTTP surface of its own. Returns (verdict, error_code) and never raises, mirroring inbox/plugins.extract_resume_text's (text, error) shape. A provider outage must be a policy decision at the call site (INBOX_TRIAGE_FAIL_OPEN in plugins.should_ingest), not a 500 on /email/fetch. """ from __future__ import annotations import logging from app.core.errors import ATSError, classify_error from inbox_classifier.agent_setup import get_classifier from inbox_classifier.decorators import email_signals from inbox_classifier.plugins import TRIAGE_MAX_BODY_CHARS, TRIAGE_MAX_SUBJECT_CHARS logger=logging.getLogger("inbox.triage") # No subject and no body: there is nothing to judge, so this is unclassifiable rather # than a "no". It routes through the fail policy, which under the default fail-open # means the mail is ingested — a signal-free message is never silently dropped. EMPTY_MESSAGE="empty_message" async def classify_email(email_data) -> tuple: """Judge one email from its subject and body. Never raises. (verdict, "") on success; (None, error_code) when the model could not be consulted or returned something unusable. """ subject,body=email_signals(email_data,TRIAGE_MAX_SUBJECT_CHARS,TRIAGE_MAX_BODY_CHARS) if not subject and not body: return None,EMPTY_MESSAGE try: # get_classifier() is inside the try on purpose: a missing OPENAI_API_KEY raises # RuntimeError from llm_setup.get_client(), and a stale OPENAI_MODEL raises # pydantic ValidationError from Settings. Both belong on the fail policy, not on # a 500 for the whole fetch round. classifier=get_classifier() verdict=await classifier.classify(subject,body) return verdict,"" except ATSError as e: logger.warning("triage failed: code=%s",e.error_code) return None,e.error_code except Exception as e: # classify_error never returns provider text. Log the exception TYPE and the code # only — never the message, which can carry prompt or body content. code,_=classify_error(e) logger.warning("triage failed: code=%s exc=%s",code,type(e).__name__) return None,code