Spaces:
Runtime error
Runtime error
File size: 4,284 Bytes
77a0a72 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """
rules_digest.py (ex utils/rules_digest.py)
──────────────────────────────────────────
Parser de `pythonisation_rules.md` + construction de digests ciblés injectés
dans les prompts. Parsé une fois à l'import, caché en module.
API publique :
RULES_BY_ID dict[str, Rule]
ALL_RULE_IDS list[str]
build_rules_digest(rule_ids) → markdown compact
AUDIT_RULES_ALWAYS list[str]
"""
from __future__ import annotations
import re
from typing import Optional, TypedDict
from app.config import RULES_MD
class Rule(TypedDict, total=False):
id: str
title: str
section: str
bad: Optional[str]
good: Optional[str]
why: Optional[str]
_RULE_HEADING = re.compile(
r"^### RÈGLE (?P<id>\d+\.\d+(?:\.\d+)?)\s*—\s*(?P<title>.+?)\s*$",
re.MULTILINE,
)
_SECTION_HEADING = re.compile(
r"^## (?P<num>\d+)\.\s*(?P<title>.+?)\s*$",
re.MULTILINE,
)
_BAD_BLOCK = re.compile(
r"\*\*❌\s*FAUTIF\*\*[^\n]*\n```(?:[a-zA-Z]*)\n(.+?)\n```",
re.DOTALL,
)
_GOOD_BLOCK = re.compile(
r"\*\*✅\s*CORRIG(?:É|EE?|É?)\*\*[^\n]*\n```(?:[a-zA-Z]*)\n(.+?)\n```",
re.DOTALL,
)
_WHY = re.compile(
r"\*\*POURQUOI\*\*\s*:\s*(.+?)(?=\n{2,}|\n#{2,3}\s|\Z)",
re.DOTALL,
)
def _parse_rules(content: str) -> dict[str, Rule]:
sections: list[tuple[int, str]] = []
for m in _SECTION_HEADING.finditer(content):
sections.append((m.start(), f"{m.group('num')}. {m.group('title').strip()}"))
rule_matches = list(_RULE_HEADING.finditer(content))
rules: dict[str, Rule] = {}
for i, m in enumerate(rule_matches):
rule_id = m.group("id")
title = m.group("title").strip()
body_start = m.end()
body_end = rule_matches[i + 1].start() if i + 1 < len(rule_matches) else len(content)
body = content[body_start:body_end].strip()
section_title = ""
for off, label in sections:
if off < m.start():
section_title = label
bad_m = _BAD_BLOCK.search(body)
good_m = _GOOD_BLOCK.search(body)
why_m = _WHY.search(body)
rules[rule_id] = Rule(
id=rule_id,
title=title,
section=section_title,
bad=bad_m.group(1).rstrip() if bad_m else None,
good=good_m.group(1).rstrip() if good_m else None,
why=why_m.group(1).strip() if why_m else None,
)
return rules
_RULES_CACHE: Optional[dict[str, Rule]] = None
def _load_rules() -> dict[str, Rule]:
global _RULES_CACHE
if _RULES_CACHE is None:
_RULES_CACHE = _parse_rules(RULES_MD.read_text(encoding="utf-8"))
return _RULES_CACHE
RULES_BY_ID: dict[str, Rule] = _load_rules()
ALL_RULE_IDS: list[str] = sorted(
RULES_BY_ID.keys(),
key=lambda s: tuple(int(p) for p in s.split(".")),
)
def build_rules_digest(rule_ids: list[str]) -> str:
"""Digest markdown compact des règles demandées (IDs inconnus ignorés)."""
if not rule_ids:
return ""
parts: list[str] = []
for rid in rule_ids:
rule = RULES_BY_ID.get(rid)
if rule is None:
continue
parts.append(f"### Règle {rule['id']} — {rule['title']}")
if rule.get("bad"):
parts.append("❌ FAUTIF :")
parts.append(f"```\n{rule['bad']}\n```")
if rule.get("good"):
parts.append("✅ CORRECT :")
parts.append(f"```\n{rule['good']}\n```")
if rule.get("why"):
parts.append(f"→ {' '.join(rule['why'].split())}")
parts.append("")
return "\n".join(parts).rstrip() + "\n"
# Règles vérifiables sur le texte assemblé seul (pas d'exec) — passe d'audit.
AUDIT_RULES_ALWAYS: list[str] = [
"2.1", # :id: vide en première ligne
"3.1", # un seul bloc Python principal
"6.1", # pas d'appels **kwargs dans {{...}}
"6.2", # pas de pxsl_format_number(**kwargs) dans {{...}}
"6.3", # variables d'affichage pré-calculées
"7.1", # pas de concaténation signe + valeur
"9.1", # paragraphes contextuels hors questionStatement
"9.4", # pas de doublons d'énoncés
"11.5", # plt.show() (pas savefig)
]
|