/** * Maps raw digest / upstream errors to clear admin-lab messages (not shown to subscribers). */ export function classifyDigestAdminError(raw: string): string { const s = raw.toLowerCase(); if (/429|rate limit|too many requests/.test(s)) { return "Groq rate limit hit — try later or check quota and DIGEST_SCORE_DELAY_MS."; } if ( /resend/i.test(raw) || /RESEND_API_KEY/i.test(raw) || (s.includes("resend") && /missing|401|403|invalid/i.test(s)) ) { return "Resend API key missing or invalid — check RESEND_API_KEY and sender domain."; } if (/SERPER|serper/i.test(raw) || s.includes("missing serper")) { return "Serper search API not configured — set SERPER_API_KEY."; } if (/groq|GROQ_API_KEY|openrouter|OPENROUTER/i.test(raw) && /key|401|403|missing/i.test(s)) { return "Scoring API key missing or rejected — check GROQ_API_KEY / OPENROUTER_API_KEY on the deployment."; } if (/insufficient credits/i.test(s)) { return "Insufficient credits for this user to run a paid digest."; } if (/no listings|no matches|passed threshold|passed today/i.test(s)) { return raw; } return raw; } export type DiscoveryFailureKind = "config" | "quota" | "timeout" | "scoring" | "unknown"; /** Classify discovery tool errors for digest handling and user messaging. */ export function classifyDiscoveryFailure(error: string): DiscoveryFailureKind { const s = error.toLowerCase(); if ( /temporarily unavailable|serper|not configured|missing serper/i.test(s) || /job discovery is temporarily unavailable/i.test(s) ) { return "config"; } if (/monthly_limit_reached|monthly limit|insufficient credits/i.test(s)) { return "quota"; } if (/timed out|timeout/i.test(s)) { return "timeout"; } if (/scoring|score failed|couldn't score/i.test(s)) { return "scoring"; } return "unknown"; }