Spaces:
Running
Running
Claude Sonnet 5.5
feat(erreurs): incidents techniques en bloc Gotham explique par l'IA + Telemetrie
001eb74 Download src/core/errors.py from klydekushy/Vortex-Flux: direct link, hf CLI and curl.
- Browser
- Download file 7.68 kB
-
https://huggingface.co/spaces/klydekushy/Vortex-Flux/resolve/main/src/core/errors.py
- Command line
-
hf download hf://spaces/klydekushy/Vortex-Flux/src/core/errors.py
-
curl -L -o errors.py https://huggingface.co/spaces/klydekushy/Vortex-Flux/resolve/main/src/core/errors.py
7.68 kB
| """ | |
| 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.<br>" | |
| "Le systeme Google limite actuellement les requetes pour eviter la surcharge.<br>" | |
| 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.<br>") | |
| return f""" | |
| <div style="background: rgba(243, 156, 18, 0.08); border: 1px solid rgba(243, 156, 18, 0.3); border-left: 4px solid #f39c12; padding: 14px 18px; margin-bottom: 16px; font-family: 'JetBrains Mono', monospace; border-radius: 0;"> | |
| <div style="color: #f39c12; font-weight: 800; font-size: 0.75rem; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 6px;"> | |
| [ AVERTISSEMENT TACTIQUE : CODE 429 ] | |
| </div> | |
| <div style="color: #a8b8c8; font-size: 0.75rem; line-height: 1.6;"> | |
| SATURATION TEMPORAIRE DES TRANSMISSIONS VERS LE CENTRE DE DONNEES.<br> | |
| {explication}<span style="color:#f39c12; font-weight: 700;">> ACTION REQUISE :</span> Maintenez la position et patientez environ 60 secondes avant de relancer. | |
| </div> | |
| </div> | |
| """ | |
| def _admin_connecte() -> bool: | |
| """Le detail technique n'est montre qu'a un admin authentifie, jamais au public.""" | |
| import streamlit as st | |
| try: | |
| return bool(st.session_state.get("authenticated")) | |
| except Exception: | |
| return False | |
| def installer_bandeau_429(): | |
| """Partout dans l'app : un st.error / st.warning (y compris st.sidebar.error, container.error...) | |
| dont le message est un depassement de quota 429 s'affiche en bandeau JAUNE explicatif | |
| au lieu d'un cadre rouge brut. Idempotent. A appeler une fois, tot dans streamlit_app.py.""" | |
| import streamlit as st | |
| from streamlit.delta_generator import DeltaGenerator | |
| if getattr(DeltaGenerator, "_vortex_429_installe", False): | |
| return | |
| def _envelopper(original, incidents=False): | |
| def _alerte(self, body, *args, **kwargs): | |
| if est_quota(body): | |
| try: | |
| return self.markdown(_html_bandeau_quota(body), unsafe_allow_html=True) | |
| except Exception: | |
| pass | |
| if incidents and _admin_connecte(): | |
| from core import incidents as _inc | |
| if _inc.est_technique(body): | |
| # Panne technique (pas une consigne de saisie) : bloc explique par l'IA. | |
| return _inc.afficher_incident(body, contexte=str(st.session_state.get("_audit_menu", "") or "")) | |
| return original(self, body, *args, **kwargs) | |
| _alerte.__wrapped__ = original | |
| return _alerte | |
| DeltaGenerator.error = _envelopper(DeltaGenerator.error, incidents=True) | |
| DeltaGenerator.warning = _envelopper(DeltaGenerator.warning) | |
| # st.error / st.warning sont des methodes deja liees au DeltaGenerator principal : on les re-lie. | |
| st.error = lambda body, *a, **k: DeltaGenerator.error(st._main, body, *a, **k) | |
| st.warning = lambda body, *a, **k: DeltaGenerator.warning(st._main, body, *a, **k) | |
| DeltaGenerator._vortex_429_installe = True | |
| def afficher_erreur_sheets(exc: Exception, contexte: str = ""): | |
| """Affiche une erreur Google Sheets de maniere lisible plutot qu'une | |
| traceback brute : bandeau JAUNE si c'est un quota depasse (429/"Quota | |
| exceeded" — l'utilisateur doit juste patienter), bandeau ROUGE sinon | |
| (erreur reelle). Meme gabarit que celui deja utilise dans | |
| modules/repayments.py et modules/request_loans.py, factorise ici pour | |
| etre reutilise partout ou une lecture/ecriture Google Sheets peut echouer. | |
| `contexte` est un libelle optionnel (ex: "chargement des remboursements") | |
| insere dans le message rouge pour aider a localiser l'incident.""" | |
| import streamlit as st | |
| erreur_str = str(exc) | |
| if est_quota(erreur_str): | |
| st.markdown(_html_bandeau_quota(erreur_str), unsafe_allow_html=True) | |
| elif _admin_connecte(): | |
| from core.incidents import afficher_incident | |
| afficher_incident(exc, contexte=contexte or str(st.session_state.get("_audit_menu", "") or "")) | |
| else: | |
| # Public : jamais de detail technique (peut contenir noms de feuilles, identifiants...). | |
| st.markdown(''' | |
| <div style="background: rgba(88, 166, 255, 0.05); border: 1px solid rgba(88, 166, 255, 0.28); border-left: 4px solid #58a6ff; padding: 14px 18px; margin-bottom: 16px; font-family: 'JetBrains Mono', monospace; border-radius: 0;"> | |
| <div style="color: #58a6ff; font-weight: 800; font-size: 0.75rem; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 6px;"> | |
| [ SERVICE INDISPONIBLE ] | |
| </div> | |
| <div style="color: #a8b8c8; font-size: 0.75rem; line-height: 1.6;"> | |
| Une erreur temporaire est survenue. Réessayez dans quelques instants. | |
| </div> | |
| </div> | |
| ''', unsafe_allow_html=True) | |