fix(M6): corrections d'audit FIXME_M6 — normalisation, secret, tests, doc
Corrige les 5 points de l'audit FIXME_M6 : 1. Normalisation des matières : fonction normalize_subject (NFKC + unification des espaces + suppression ponctuation + minuscule) partagée par la génération d'ID et le futur comparateur M8. 2. Expurgation du secret dans l'erreur de collision d'IDs : redact_secrets enveloppe l'identifiant dans le message. 3. Test even/odd avec même matière pour isoler la parité comme seul différenciateur d'ID ; tests de normalisation (casse, espaces, Unicode) ; test de non-fuite de secret. 4. TODO.md M6 : 8 items cochés après validation. 5. GUIDE_DEV §8.4 : bloc de code corrigé (clôture, types Lesson/ TheoreticalLesson, comparaison des horaires en minutes, début ET fin, référence à normalize_subject). Co-authored-by: opencode/coder <coder@agents.invalid> Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
This commit is contained in:
@@ -10,6 +10,8 @@ plage de dates. Le filtrage tient compte du jour de la semaine, de la parité de
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import date, time, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
@@ -24,6 +26,25 @@ from pronote_sync.utils.redaction import redact_exception, redact_secrets
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_subject(subject: str) -> str:
|
||||
"""Normalise une matière pour le matching déterministe.
|
||||
|
||||
Applique la normalisation Unicode NFKC, unifie les espaces (y compris
|
||||
tabulations et espaces insécables), supprime la ponctuation et met la
|
||||
chaîne en minuscules. Deux représentations visuellement identiques d'une
|
||||
même matière produisent ainsi la même forme normalisée.
|
||||
|
||||
:param subject: La matière brute.
|
||||
:return: La forme normalisée (NFKC, espaces unifiés, sans ponctuation, minuscule).
|
||||
:rtype: str
|
||||
"""
|
||||
normalized = unicodedata.normalize("NFKC", subject)
|
||||
normalized = re.sub(r"\s+", " ", normalized).strip()
|
||||
normalized = re.sub(r"[^\w\s]", "", normalized)
|
||||
normalized = re.sub(r"\s+", " ", normalized).strip()
|
||||
return normalized.lower()
|
||||
|
||||
|
||||
def _generate_id(entry: TheoreticalLessonEntry) -> str:
|
||||
"""Génère un identifiant déterministe pour une entrée de cours.
|
||||
|
||||
@@ -36,7 +57,7 @@ def _generate_id(entry: TheoreticalLessonEntry) -> str:
|
||||
:return: Identifiant déterministe unique.
|
||||
:rtype: str
|
||||
"""
|
||||
subject_slug = entry.subject.lower().strip().replace(" ", "-")
|
||||
subject_slug = normalize_subject(entry.subject).replace(" ", "-")
|
||||
return f"theoretical:{entry.week}:{entry.day_of_week}:{entry.start_time}-{entry.end_time}:{subject_slug}"
|
||||
|
||||
|
||||
@@ -111,7 +132,7 @@ class JsonTheoreticalAgendaProvider:
|
||||
if effective_id in seen_ids:
|
||||
raise PronoteSyncError(
|
||||
f"Conflit d'identifiant dans l'agenda théorique : "
|
||||
f"l'identifiant '{effective_id}' est utilisé par plusieurs leçons. "
|
||||
f"l'identifiant '{redact_secrets(effective_id)}' est utilisé par plusieurs leçons. "
|
||||
f"Fournissez des identifiants explicites uniques."
|
||||
) from None
|
||||
seen_ids.add(effective_id)
|
||||
|
||||
Reference in New Issue
Block a user