fix(ical): fiabiliser l’extraction des devoirs et événements Pronote 2026 #28

Closed
opened 2026-09-12 10:36:08 +02:00 by Codex · 3 comments
Collaborator

Objectif

Rendre le parsing iCal Pronote tolérant aux variations observées dans les exports récents et préserver correctement le contexte des devoirs, afin que le digest ne perde ni événement scolaire ni devoir distinct.

Constats dans le code actuel

Événements scolaires

parse_ical() classe uniquement les catégories exactes Congés et Vacances comme congés, puis les représente toutes par SchoolEventKind.HOLIDAY.

Le parser ne distingue donc pas les jours fériés des vacances et risque d'ignorer une catégorie Pronote comme Jours fériés. Le modèle possède pourtant déjà PUBLIC_HOLIDAY.

En-tête de description

parse_header() reconnaît Matière, Professeur(s), Salle(s) et Groupe, mais pas les champs complémentaires observés dans les exports, notamment Partie(s) de classe. Les champs inconnus sont ignorés sans signalement.

Sections HTML

parse_body() dépend de motifs très stricts : balise <strong> sans attribut, libellé et espaces précis, date obligatoirement au format JJ/MM/AAAA. Une variation mineure de HTML générée par Pronote peut faire disparaître le contenu ou les devoirs sans erreur explicite.

Déduplication et date de distribution

collect_homeworks() déduplique uniquement sur le texte normalisé. Deux devoirs différents ayant la même consigne, mais concernant des matières ou enseignants différents, peuvent donc être fusionnés.

Inversement, Pronote peut recopier le même devoir dans plusieurs cours ; le contexte enseignant doit participer à la clé de déduplication. La date assigned_on d’un bloc « Pour le » est actuellement déduite de lesson.start.date(), alors que la date explicite « Donné le » peut être différente.

Preuves externes

Le projet pronote-digest documente des exports Pronote 2026 observés sur le terrain :

  • l’en-tête peut contenir Matière :, Professeur(s) :, Salle(s) :, Groupe : et Partie(s) de classe : ;
  • les catégories d’événements incluent Jours fériés ;
  • un même devoir peut être recopié dans plusieurs cours ;
  • les blocs « Pour le » et « Donné le » portent deux informations distinctes (documentation d’architecture).

Son parseur conserve séparément le HTML et le texte, attribue un décalage explicite Europe/Paris aux horaires et déduplique en tenant compte du contexte enseignant (parse.ts).

Risques actuels

  • journée fériée traitée comme vacances ou perdue ;
  • contenu pédagogique/devoir absent du digest après une variation de balise ou d’espacement ;
  • devoirs distincts fusionnés à tort ;
  • date de distribution inventée à partir du jour du cours ;
  • impossibilité de diagnostiquer un nouveau libellé Pronote ignoré silencieusement.

Périmètre attendu

  1. Centraliser les libellés et catégories Pronote avec une tolérance contrôlée à la casse, aux espaces et aux variantes singulier/pluriel.
  2. Distinguer HOLIDAY et PUBLIC_HOLIDAY, en conservant la date de fin iCalendar comme borne exclusive.
  3. Préserver les champs d’en-tête utiles, ou documenter explicitement ceux qui ne doivent pas entrer dans le modèle métier.
  4. Remplacer les regex HTML trop fragiles par un découpage par sections générées par Pronote, tout en conservant le HTML original pour les devoirs.
  5. Dédupliquer les copies du même devoir sans fusionner deux devoirs de matières/enseignants différents.
  6. Utiliser la date explicite « Donné le » quand elle existe ; ne jamais fabriquer une date de distribution silencieusement si elle est absente.
  7. Conserver des erreurs ou métriques de parsing observables pour les structures inconnues, sans divulguer de données personnelles.

Critères d’acceptation

  • Fixture : catégorie Jours fériésPUBLIC_HOLIDAY.
  • Fixture : catégories avec variantes d’annulation/déplacement → statut correct.
  • Fixture : Professeur(s), Salle(s), Partie(s) de classe et entités HTML.
  • Fixture : balises strong avec variations d’espacement ou attributs sans perte de sections.
  • Fixture : même texte et même enseignant recopiés → un seul devoir.
  • Fixture : même texte mais enseignants/matières différents → devoirs conservés séparément.
  • Fixture : dates « Pour le » et « Donné le » différentes → due_on et assigned_on corrects.
  • HTML conservé dans html, texte nettoyé dans text, sans script ni style exécutable.
  • Aucun token iCal, nom réel ou contenu personnel non anonymisé dans les fixtures.
  • Tests de non-régression sur les deux fixtures existantes et validation ruff/mypy/tests.

Conclusion opérationnelle

Prioriser ce ticket avant d’ajouter de nouveaux consommateurs de devoirs : le contrat iCal doit être fidèle et explicite. Toute donnée absente ou libellé inconnu doit produire un comportement documenté et observable, jamais une liste partielle silencieuse.

## Objectif Rendre le parsing iCal Pronote tolérant aux variations observées dans les exports récents et préserver correctement le contexte des devoirs, afin que le digest ne perde ni événement scolaire ni devoir distinct. ## Constats dans le code actuel ### Événements scolaires `parse_ical()` classe uniquement les catégories exactes `Congés` et `Vacances` comme congés, puis les représente toutes par `SchoolEventKind.HOLIDAY`. Le parser ne distingue donc pas les jours fériés des vacances et risque d'ignorer une catégorie Pronote comme `Jours fériés`. Le modèle possède pourtant déjà `PUBLIC_HOLIDAY`. ### En-tête de description `parse_header()` reconnaît `Matière`, `Professeur(s)`, `Salle(s)` et `Groupe`, mais pas les champs complémentaires observés dans les exports, notamment `Partie(s) de classe`. Les champs inconnus sont ignorés sans signalement. ### Sections HTML `parse_body()` dépend de motifs très stricts : balise `<strong>` sans attribut, libellé et espaces précis, date obligatoirement au format `JJ/MM/AAAA`. Une variation mineure de HTML générée par Pronote peut faire disparaître le contenu ou les devoirs sans erreur explicite. ### Déduplication et date de distribution `collect_homeworks()` déduplique uniquement sur le texte normalisé. Deux devoirs différents ayant la même consigne, mais concernant des matières ou enseignants différents, peuvent donc être fusionnés. Inversement, Pronote peut recopier le même devoir dans plusieurs cours ; le contexte enseignant doit participer à la clé de déduplication. La date `assigned_on` d’un bloc « Pour le » est actuellement déduite de `lesson.start.date()`, alors que la date explicite « Donné le » peut être différente. ## Preuves externes Le projet [pronote-digest](https://github.com/yoanbernabeu/pronote-digest) documente des exports Pronote 2026 observés sur le terrain : - l’en-tête peut contenir `Matière :`, `Professeur(s) :`, `Salle(s) :`, `Groupe :` et `Partie(s) de classe :` ; - les catégories d’événements incluent `Jours fériés` ; - un même devoir peut être recopié dans plusieurs cours ; - les blocs « Pour le » et « Donné le » portent deux informations distinctes ([documentation d’architecture](https://github.com/yoanbernabeu/pronote-digest/blob/main/docs/architecture.md)). Son parseur conserve séparément le HTML et le texte, attribue un décalage explicite `Europe/Paris` aux horaires et déduplique en tenant compte du contexte enseignant ([parse.ts](https://raw.githubusercontent.com/yoanbernabeu/pronote-digest/main/src/sources/pronote/parse.ts)). ## Risques actuels - journée fériée traitée comme vacances ou perdue ; - contenu pédagogique/devoir absent du digest après une variation de balise ou d’espacement ; - devoirs distincts fusionnés à tort ; - date de distribution inventée à partir du jour du cours ; - impossibilité de diagnostiquer un nouveau libellé Pronote ignoré silencieusement. ## Périmètre attendu 1. Centraliser les libellés et catégories Pronote avec une tolérance contrôlée à la casse, aux espaces et aux variantes singulier/pluriel. 2. Distinguer `HOLIDAY` et `PUBLIC_HOLIDAY`, en conservant la date de fin iCalendar comme borne exclusive. 3. Préserver les champs d’en-tête utiles, ou documenter explicitement ceux qui ne doivent pas entrer dans le modèle métier. 4. Remplacer les regex HTML trop fragiles par un découpage par sections générées par Pronote, tout en conservant le HTML original pour les devoirs. 5. Dédupliquer les copies du même devoir sans fusionner deux devoirs de matières/enseignants différents. 6. Utiliser la date explicite « Donné le » quand elle existe ; ne jamais fabriquer une date de distribution silencieusement si elle est absente. 7. Conserver des erreurs ou métriques de parsing observables pour les structures inconnues, sans divulguer de données personnelles. ## Critères d’acceptation - [ ] Fixture : catégorie `Jours fériés` → `PUBLIC_HOLIDAY`. - [ ] Fixture : catégories avec variantes d’annulation/déplacement → statut correct. - [ ] Fixture : `Professeur(s)`, `Salle(s)`, `Partie(s) de classe` et entités HTML. - [ ] Fixture : balises `strong` avec variations d’espacement ou attributs sans perte de sections. - [ ] Fixture : même texte et même enseignant recopiés → un seul devoir. - [ ] Fixture : même texte mais enseignants/matières différents → devoirs conservés séparément. - [ ] Fixture : dates « Pour le » et « Donné le » différentes → `due_on` et `assigned_on` corrects. - [ ] HTML conservé dans `html`, texte nettoyé dans `text`, sans script ni style exécutable. - [ ] Aucun token iCal, nom réel ou contenu personnel non anonymisé dans les fixtures. - [ ] Tests de non-régression sur les deux fixtures existantes et validation `ruff`/mypy/tests. ## Conclusion opérationnelle Prioriser ce ticket avant d’ajouter de nouveaux consommateurs de devoirs : le contrat iCal doit être fidèle et explicite. Toute donnée absente ou libellé inconnu doit produire un comportement documenté et observable, jamais une liste partielle silencieuse.
Codex added the investigationbug labels 2026-09-12 10:36:08 +02:00
Codex self-assigned this 2026-09-12 10:36:08 +02:00
Author
Collaborator

Complément — salle et fuseau horaire

La comparaison avec le parseur de pronote-digest apporte deux cas à intégrer au périmètre de #28 :

  • le parseur externe lit d’abord la propriété iCalendar LOCATION, puis utilise les salles extraites de DESCRIPTION en repli ; le parseur local ne lit actuellement pas LOCATION. Une salle présente uniquement dans cette propriété est donc perdue ;
  • le contrat externe convertit les horaires vers Europe/Paris et valide des dates ISO avec décalage explicite. Le modèle local accepte des datetime naïves, ce qui peut provoquer des comparaisons ou des UID différents selon le fuseau du processus.

Critères complémentaires :

  • LOCATION multi-valeurs et paramètres iCalendar sont décodés sans écraser une salle déjà mieux qualifiée.
  • Un horaire UTC et un horaire TZID=Europe/Paris représentant le même instant sont interprétés selon le contrat local documenté.
  • Les datetime sortant du parser ont un fuseau explicite, ou le choix d’interprétation des horaires flottants est documenté et testé.
  • Les UIDs déterministes restent stables lorsque seul le fuseau de représentation change.

Conclusion opérationnelle : traiter LOCATION et le fuseau avant de considérer le parser iCal comme compatible avec des exports d’établissements différents.

## Complément — salle et fuseau horaire La comparaison avec le parseur de [pronote-digest](https://raw.githubusercontent.com/yoanbernabeu/pronote-digest/main/src/sources/pronote/parse.ts) apporte deux cas à intégrer au périmètre de #28 : - le parseur externe lit d’abord la propriété iCalendar `LOCATION`, puis utilise les salles extraites de `DESCRIPTION` en repli ; le parseur local ne lit actuellement pas `LOCATION`. Une salle présente uniquement dans cette propriété est donc perdue ; - le contrat externe convertit les horaires vers `Europe/Paris` et valide des dates ISO avec décalage explicite. Le modèle local accepte des `datetime` naïves, ce qui peut provoquer des comparaisons ou des UID différents selon le fuseau du processus. Critères complémentaires : - [ ] `LOCATION` multi-valeurs et paramètres iCalendar sont décodés sans écraser une salle déjà mieux qualifiée. - [ ] Un horaire UTC et un horaire `TZID=Europe/Paris` représentant le même instant sont interprétés selon le contrat local documenté. - [ ] Les `datetime` sortant du parser ont un fuseau explicite, ou le choix d’interprétation des horaires flottants est documenté et testé. - [ ] Les UIDs déterministes restent stables lorsque seul le fuseau de représentation change. Conclusion opérationnelle : traiter `LOCATION` et le fuseau avant de considérer le parser iCal comme compatible avec des exports d’établissements différents.
Author
Collaborator

Complément après lecture ciblée du code

Le défaut de conservation HTML est plus direct que ne le laissait entendre le constat initial :

  • parse_body() applique _strip_html() aux blocs « Pour le » et « Donné le » ;
  • parse_homework_blocks() construit ensuite HomeworkBlock avec html=text, donc le HTML original est déjà perdu avant collect_homeworks() ;
  • collect_homeworks() transmet ensuite block.html à Homework.html, ce qui ne peut pas restaurer les balises ou les liens Pronote.

Le ticket doit donc préciser où conserver deux représentations dès l'extraction : texte nettoyé pour l'affichage/déduplication et fragment HTML original assaini pour les consommateurs qui en ont besoin. Les tests doivent vérifier cette propriété de bout en bout, et pas seulement le résultat de _strip_html().

## Complément après lecture ciblée du code Le défaut de conservation HTML est plus direct que ne le laissait entendre le constat initial : - parse_body() applique _strip_html() aux blocs « Pour le » et « Donné le » ; - parse_homework_blocks() construit ensuite HomeworkBlock avec html=text, donc le HTML original est déjà perdu avant collect_homeworks() ; - collect_homeworks() transmet ensuite block.html à Homework.html, ce qui ne peut pas restaurer les balises ou les liens Pronote. Le ticket doit donc préciser où conserver deux représentations dès l'extraction : texte nettoyé pour l'affichage/déduplication et fragment HTML original assaini pour les consommateurs qui en ont besoin. Les tests doivent vérifier cette propriété de bout en bout, et pas seulement le résultat de _strip_html().
Codex added the area:parsingarea:pronotepriority:critical labels 2026-09-12 13:14:35 +02:00
Author
Collaborator

Ticket traité par la PR #31, fusionnée en squash (d45d38d365bbc721040b60e329c429f4ea234cfd). Les variantes iCal, les événements scolaires et la déduplication des devoirs sont couvertes ; la validation intégrée finale est verte. Clôture manuelle car Gitea n’a pas appliqué cette seconde référence Closes du corps de la PR.

Ticket traité par la PR #31, fusionnée en squash (`d45d38d365bbc721040b60e329c429f4ea234cfd`). Les variantes iCal, les événements scolaires et la déduplication des devoirs sont couvertes ; la validation intégrée finale est verte. Clôture manuelle car Gitea n’a pas appliqué cette seconde référence `Closes` du corps de la PR.
Codex closed this issue 2026-09-12 14:56:26 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: AntoineVe/college-infos#28