"""
JOURNALISATION DES ERREURS AVALEES
=====================================
Beaucoup de blocs try/except de l'app convertissent une donnee illisible
(revenu, date, montant...) en valeur par defaut (0, 1, 30 jours...) pour ne
pas interrompre l'ecran en cours. C'est un choix legitime pour ne pas
bloquer un gerant sur une ligne Google Sheets mal saisie, MAIS un `except:`
nu qui ne log rien rend ces incidents invisibles : personne ne sait qu'un
revenu n'a pas pu etre lu, et le calcul en aval (endettement, taux...) se
poursuit avec une valeur par defaut sans que quiconque soit prevenu.
log_swallowed() ne change pas le comportement de repli (l'app continue),
mais ecrit l'erreur dans les logs du process (visibles sur Hugging Face
Space -> Logs) avec le contexte, pour qu'un incident recurrent soit
detectable au lieu de disparaitre silencieusement.
"""
import logging
_logger = logging.getLogger("vortex")
if not _logger.handlers:
_handler = logging.StreamHandler()
_handler.setFormatter(logging.Formatter("[VORTEX] %(levelname)s %(message)s"))
_logger.addHandler(_handler)
_logger.setLevel(logging.WARNING)
def log_swallowed(context: str, exc: Exception):
"""A appeler dans un except pour tracer une erreur avant de retomber sur
une valeur par defaut. `context` doit identifier precisement l'endroit
et, si possible, la donnee en cause (ex: "revenu client CLI-2026-0004")."""
_logger.warning("%s : %s: %s", context, type(exc).__name__, exc)
try:
from core import audit_log
audit_log.evenement("ERREUR", "ERREUR_AVALEE", cible=context, statut="ECHEC",
detail=f"{type(exc).__name__}: {exc}")
except Exception:
pass
_MARQUEURS_QUOTA = ("quota exceeded", "resource_exhausted", "too many requests", "rate limit", "ratelimit")
def est_quota(erreur) -> bool:
"""Vrai si `erreur` (exception ou texte) est un depassement de quota : code HTTP 429
(« [429] », « code 429 », « error 429 ») ou formulation Google/LLM equivalente.
Un simple nombre contenant 429 (ex. « 1 429 000 XOF ») n'est PAS un quota."""
import re
texte = str(erreur or "")
if re.search(r"\[429\]|(?:code|error|erreur|status|http)\W{0,3}429|429\W{0,3}(?:too many|quota|resource)",
texte, re.IGNORECASE):
return True
return any(m in texte.lower() for m in _MARQUEURS_QUOTA)
def _html_bandeau_quota(texte=""):
sheets = "sheets.googleapis" in str(texte) or "Read requests" in str(texte) or not str(texte).strip()
explication = (
"Quota Google Sheets depasse : trop de lectures par minute (limite partagee entre tous les utilisateurs "
"de l'application). Vos donnees ne sont pas perdues.
"
"Le systeme Google limite actuellement les requetes pour eviter la surcharge.
"
if sheets else
"Un service externe (IA, email ou donnees) limite temporairement les requetes : trop d'appels en peu de temps. "
"Vos donnees ne sont pas perdues.
")
return f"""