wilenPyxi commited on
Commit
7e0a262
·
1 Parent(s): f63a47d

QCM : questions qualitatives + reponse=objet complet + notations par langue (intervalles/decimales via variables FR/EN, pas de py) ; garde-fou : skip du RAG si quota OpenAI epuise (429)

Browse files
app/config.py CHANGED
@@ -23,7 +23,7 @@ TEMPLATES_DIR = PACKAGE_DIR / "web" / "templates"
23
  # ── Version applicative (exposée par /health pour vérifier un déploiement) ───
24
  # Bumper à chaque déploiement significatif : permet de répondre « à jour ? »
25
  # sans se connecter (curl /health → champ "version").
26
- APP_VERSION = "2026-07-07blot pythonisation + mode « Meilleur » = Opus 4.8"
27
 
28
  # ── Convention MyST (vérifiée empiriquement : 222/222 exemples plateforme) ───
29
  # Bloc {python} = 4 backticks ; enveloppe {exercise} = 5 backticks.
 
23
  # ── Version applicative (exposée par /health pour vérifier un déploiement) ───
24
  # Bumper à chaque déploiement significatif : permet de répondre « à jour ? »
25
  # sans se connecter (curl /health → champ "version").
26
+ APP_VERSION = "2026-07-07cQCM (qualitatif/bilingue/objet complet) + skip quota OpenAI"
27
 
28
  # ── Convention MyST (vérifiée empiriquement : 222/222 exemples plateforme) ───
29
  # Bloc {python} = 4 backticks ; enveloppe {exercise} = 5 backticks.
app/pipeline/analyze.py CHANGED
@@ -28,6 +28,17 @@ from app.rag.notions import enrich_exercise_with_notions
28
 
29
  logger = logging.getLogger(__name__)
30
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  def _rules_menu() -> str:
33
  return "\n".join(f" - {rid} — {RULES_BY_ID[rid]['title']}" for rid in ALL_RULE_IDS)
@@ -90,7 +101,8 @@ def run_analysis_phase(content: str, model_idx: int,
90
  max_tokens=6096,
91
  )
92
  f_notions = pool.submit(enrich_exercise_with_notions, content, xlsx_path=NOTIONS_XLSX)
93
- f_functions = pool.submit(
 
94
  retrieve_functions_context,
95
  exercise=content,
96
  embedding_model=RAG_EMBEDDING_MODEL,
@@ -106,11 +118,21 @@ def run_analysis_phase(content: str, model_idx: int,
106
  logger.warning("Retriever de notions en échec (%s) — contexte vide.", e)
107
  notions_ctx, lists_of_notions = "", ""
108
 
109
- try:
110
- functions_ctx = f_functions.result()["catalogue"]
111
- except Exception as e:
112
- logger.warning("RAG fonctions en échec (%s) — catalogue vide.", e)
113
- functions_ctx = ""
 
 
 
 
 
 
 
 
 
 
114
 
115
  analysis = _parse_analysis(raw_analysis, content)
116
  logger.info("Analyse : type=%s, %d variables, %d règles ciblées",
 
28
 
29
  logger = logging.getLogger(__name__)
30
 
31
+ # Garde-fou : dès que les embeddings OpenAI renvoient un quota épuisé (429), on
32
+ # arrête d'appeler le RAG fonctions pour le reste de la session (sinon chaque job
33
+ # repaie 3 retries + ~10 s pour un catalogue de toute façon vide). Réactivé au
34
+ # prochain démarrage (une fois le compte OpenAI rechargé).
35
+ _RAG_STATE = {"off": False}
36
+
37
+
38
+ def _is_quota_error(exc: Exception) -> bool:
39
+ s = str(exc).lower()
40
+ return "insufficient_quota" in s or "quota" in s or "429" in s
41
+
42
 
43
  def _rules_menu() -> str:
44
  return "\n".join(f" - {rid} — {RULES_BY_ID[rid]['title']}" for rid in ALL_RULE_IDS)
 
101
  max_tokens=6096,
102
  )
103
  f_notions = pool.submit(enrich_exercise_with_notions, content, xlsx_path=NOTIONS_XLSX)
104
+ # RAG fonctions : sauté si les embeddings OpenAI sont déjà « à sec ».
105
+ f_functions = None if _RAG_STATE["off"] else pool.submit(
106
  retrieve_functions_context,
107
  exercise=content,
108
  embedding_model=RAG_EMBEDDING_MODEL,
 
118
  logger.warning("Retriever de notions en échec (%s) — contexte vide.", e)
119
  notions_ctx, lists_of_notions = "", ""
120
 
121
+ if f_functions is None:
122
+ functions_ctx = "" # RAG désactivé (quota OpenAI épuisé plus tôt)
123
+ else:
124
+ try:
125
+ functions_ctx = f_functions.result()["catalogue"]
126
+ except Exception as e:
127
+ functions_ctx = ""
128
+ if _is_quota_error(e):
129
+ _RAG_STATE["off"] = True
130
+ logger.warning(
131
+ "RAG fonctions DÉSACTIVÉ pour la session : quota "
132
+ "d'embeddings OpenAI épuisé (429). Recharge le compte "
133
+ "OpenAI (platform.openai.com/billing) pour le réactiver.")
134
+ else:
135
+ logger.warning("RAG fonctions en échec (%s) — catalogue vide.", e)
136
 
137
  analysis = _parse_analysis(raw_analysis, content)
138
  logger.info("Analyse : type=%s, %d variables, %d règles ciblées",
app/pipeline/prompts.py CHANGED
@@ -744,6 +744,33 @@ questionHint : indice de DÉMARRAGE qui ne révèle jamais la réponse (cf. exem
744
  validés — tous en fournissent un) ; VERBATIM si la source en a un ; vide
745
  seulement si aucun indice pertinent.
746
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
748
  """ # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
749
 
 
744
  validés — tous en fournissent un) ; VERBATIM si la source en a un ; vide
745
  seulement si aucun indice pertinent.
746
 
747
+ QUESTION QUALITATIVE (converge/diverge, monotone ou non, bornée, vrai/faux, « la
748
+ limite existe-t-elle ? »…) → options TEXTUELLES bilingues {fr}`…`{en}`…`. Bonne
749
+ réponse = la conclusion correcte ; distracteurs = conclusions ERRONÉES crédibles
750
+ (« diverge vers +∞ », « croissante », « bornée par… »), en grille miroir. Pas de
751
+ valeur numérique dans ces options.
752
+
753
+ RÉPONSE = OBJET COMPLET : chaque option porte l'objet ENTIER visé par la question
754
+ — l'intervalle complet, l'ensemble S = {…} complet, le polynôme entier, la valeur
755
+ finale — reconstitué depuis les variables Python, JAMAIS un fragment (tout
756
+ l'intérêt du QCM : on présente la réponse entière, plus rien à saisir).
757
+
758
+ BILINGUE — NOTATIONS PROPRES À CHAQUE LANGUE (sinon la réponse s'affiche FAUX ;
759
+ vaut pour l'énoncé, CHAQUE mcqAnswer — bonne réponse ET distracteurs — et la
760
+ detailedSolution) :
761
+ • Intervalle : FR = crochets + point-virgule (borne exclue = crochet RETOURNÉ,
762
+ ex. `\\left] a \\,;\\, b \\right]`) ; EN = parenthèse/crochet + virgule
763
+ (ex. `(a, b]` ; ∞ toujours en parenthèse). → deux variables d'affichage
764
+ `interAffFr`/`interAffEn` (ou `pxs_Interval(...).print()` rendu par langue),
765
+ la bonne injectée dans CHAQUE rôle. JAMAIS la même notation dans les deux.
766
+ • Décimale : FR = virgule, EN = point. Un décimal affiché dans les DEUX langues
767
+ via UNE SEULE variable figée = BUG (il reste bloqué sur une langue). → deux
768
+ variables `valAffFr` (virgule) / `valAffEn` (point), la bonne dans chaque
769
+ rôle. (`\\py{}` est INTERDIT dans l'app : c'est la voie « une variable par
770
+ langue » qui règle le bilingue, pas `\\py{}`.)
771
+ • Entiers et coefficients LaTeX (`lc`/`pxsl_format_number`) : aucun séparateur
772
+ décimal → une seule variable, injectée telle quelle dans les deux langues.
773
+
774
  L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
775
  """ # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
776