docs(planning): préciser relais Pronote et créneaux protégés
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run

This commit is contained in:
root 2026-08-22 22:11:50 +00:00
parent 8e19476bb3
commit df9a402d27

View file

@ -48,42 +48,90 @@ résolue avant d'automatiser la disponibilité.
## C. Principe de vérité interne ## C. Principe de vérité interne
La GMAO possède des créneaux internes d'occupation. Chaque créneau conserve La GMAO possède des créneaux internes d'occupation. Chaque créneau conserve
sa source : `manual`, `pronote`, `import` ou une future source. Les champs une provenance (`manual`, `pronote`, `import`) et, conceptuellement, un
conceptuels sont `source`, `external_id`, `last_synced_at`, booléen `protected_from_sync`, un `external_id`, les horodatages de
`source_updated_at`, période de validité et statut actif. Le métier parle de synchronisation et une période de validité.
créneau d'occupation interne ; il ne dépend jamais d'une réponse en direct de
Pronote.
L'application doit démarrer et rester pleinement utilisable avec La distinction métier est la suivante :
`PRONOTE_ENABLED=false`, sans token et sans synchronisation.
- **manuel initial** : `source=manual`, protection désactivée ; il permet de
démarrer avant Pronote et peut être supplanté par une donnée Pronote fiable ;
- **manuel protégé** : `source=manual`, protection activée ; il représente une
décision locale explicite et ne peut être écrasé automatiquement ;
- **Pronote** : `source=pronote`, géré uniquement par la synchronisation qui
l'a créé ;
- **import** : donnée issue d'une autre importation connue ou historique.
L'occupation active présentée au moteur de maintenance est une vue résolue :
elle ne contient pas deux fois un créneau manuel initial et son équivalent
Pronote. Pronote est une source de synchronisation, jamais le moteur de
planning de la GMAO. L'application doit démarrer et rester pleinement
utilisable avec `PRONOTE_ENABLED=false`, sans token et sans synchronisation.
## D. Saisie manuelle en début d'année ## D. Saisie manuelle en début d'année
Une fonction de gestion crée les `RoomSchedule` de source `manual` à partir Une fonction de gestion crée des `RoomSchedule` de source `manual` à partir
des salles, périodes, jours, heures et types d'occupation : cours, réunion, des salles, périodes, jours, heures et types d'occupation : cours, réunion,
examen, réservation, travaux, intervention, fermeture ou autre. Les règles de examen, réservation, travaux, intervention, fermeture ou autre. Par défaut,
validation refusent début ≥ fin et signalent les chevauchements. Une saisie une saisie de cours destinée à l'initialisation est **non protégée**. Pour une
manuelle est toujours conservée, même lorsqu'une source externe est absente. réunion exceptionnelle, un examen local, des travaux, une réservation,
intervention ou fermeture, l'utilisateur peut cocher une formulation métier
telle que « Ne pas modifier automatiquement ce créneau ».
La validation refuse début ≥ fin et signale les chevauchements. L'interface
affiche « Saisi manuellement » et, si nécessaire, « Protégé des mises à jour
automatiques » ; elle ne montre jamais le nom technique
`protected_from_sync`.
## E. Pronote facultatif ## E. Pronote facultatif
Pronote peut fournir des créneaux après configuration, mais ne possède jamais Lorsque Pronote devient disponible et fiable, il peut prendre progressivement
la source interne de vérité. La synchronisation ne doit lire/écrire que les le relais sur les créneaux manuels **non protégés** correspondant à son emploi
créneaux qu'elle a créés (`source=pronote`, `external_id` connu). du temps. Il ne modifie ni ne supprime les créneaux manuels protégés. Il ne
possède jamais la source interne de vérité : le service de synchronisation
résout d'abord les provenances et les conflits, puis le moteur consomme la vue
interne active.
## F. Synchronisation et conflits ## F. Synchronisation et conflits
- Créneau manuel : jamais modifié ni supprimé automatiquement par Pronote. ### Rapprochement déterministe
- Créneau Pronote identique : mise à jour de ses champs et de
`last_synced_at`. Un créneau Pronote peut être rapproché automatiquement d'un manuel initial
- Créneau supprimé côté Pronote : désactivation du seul créneau Pronote uniquement si les critères stables concordent : même salle, même période de
correspondant ; aucune suppression manuelle. validité, même jour/date et horaires identiques ou suffisamment proches,
- Collision manuel/Pronote : le manuel gagne, le conflit est signalé à complétés si disponibles par classe/groupe, matière et enseignant. La V1 ne
l'utilisateur avec les deux provenances. déduit pas une correspondance à partir d'un seul critère. Si la correspondance
- Pronote indisponible : conserver les dernières données importées, marquer est ambiguë, elle conserve les deux lignes, signale un conflit et demande une
leur fraîcheur et afficher un avertissement ; ne jamais vider le planning. validation humaine.
- Donnée Pronote obsolète : elle peut être désactivée selon une politique de
rétention explicite, jamais effacée silencieusement. ### Règles
- Manuel initial non protégé + Pronote correspondant : le créneau Pronote
devient actif et le manuel initial est marqué supplanté/archivé ; il n'y a
pas de doublon dans la vue d'occupation.
- Manuel initial non protégé + horaire Pronote modifié : le Pronote remplace
l'ancien intervalle, qui n'est plus considéré comme une occupation active.
- Manuel protégé + Pronote chevauchant : le manuel reste actif, Pronote reste
visible comme conflit ; rien n'est écrasé automatiquement.
- Créneau Pronote identique ou modifié : seule la ligne `source=pronote` avec
`external_id` correspondant est mise à jour et son `last_synced_at` avancé.
- Suppression côté Pronote : désactivation de la ligne Pronote propriétaire.
Un manuel initial supplanté reste archivé comme historique et ne redevient
actif automatiquement que si une règle de réactivation explicite est
validée ; par défaut, l'interface propose à l'utilisateur de le restaurer.
Aucun manuel protégé n'est supprimé.
- Pronote indisponible : conserver tous les créneaux actifs connus, afficher
la date de dernière synchronisation et un avertissement de fraîcheur ; ne
jamais vider le planning.
Chaque transition laisse une trace minimale : créé manuellement, protégé ou
déprotégé, repris par Pronote, modifié par synchronisation, conflit détecté ou
désactivé car absent de Pronote. Cette trace doit rester compréhensible sans
imposer un moteur d'audit complexe en V1.
Le moteur de maintenance demande uniquement « quels sont les créneaux
d'occupation internes actifs de cette salle ? ». Il ne connaît ni la priorité
Pronote ni les règles de rapprochement.
## G. Disponibilité des salles ## G. Disponibilité des salles
@ -188,9 +236,10 @@ par deux affectations liées à la même activité.
Réutiliser au maximum les tables présentes : Réutiliser au maximum les tables présentes :
1. **Étendre `RoomSchedule`** : `source`, `external_id`, horodatage de 1. **Étendre `RoomSchedule`** : `source`, `protected_from_sync`,
synchronisation, validité, type/libellé et statut actif. Index futur sur `external_id`, horodatage de synchronisation, validité, type/libellé,
`(room_id, week_start, day_of_week, start_time, end_time, source)`. statut actif et, si nécessaire, référence au créneau supplanté. Index
futur sur `(room_id, week_start, day_of_week, start_time, end_time, source)`.
2. **Conserver `ScheduledTask`** comme échéance/exécution préventive ; ajouter 2. **Conserver `ScheduledTask`** comme échéance/exécution préventive ; ajouter
ultérieurement contrainte de priorité et fenêtre si les champs existants ne ultérieurement contrainte de priorité et fenêtre si les champs existants ne
suffisent pas. suffisent pas.
@ -209,11 +258,14 @@ que `PlanningItem` ne peut pas être consolidé.
## Q. Migrations futures ## Q. Migrations futures
Les extensions ci-dessus nécessiteront des migrations Alembic non destructives, Une migration non destructive ajoutera à `RoomSchedule` la provenance, la
avec valeurs par défaut (`source=manual`, statut actif) et backfill explicite protection, les identifiants/horodatages de synchronisation, la validité et le
des `RoomSchedule` existants. Les données Pronote historiques ne doivent pas statut. Les lignes existantes seront initialisées avec `source=manual` et
être confondues avec le manuel : si leur origine est inconnue, les marquer `protected_from_sync=false`, puis un écran de validation permettra de protéger
`import` et demander validation. les exceptions locales. Les données Pronote historiques d'origine inconnue
seront marquées `import` et soumises à validation, jamais fusionnées à
l'aveugle. Le statut supplanté/archivé doit conserver le lien historique sans
réactiver automatiquement un faux créneau.
## R. Algorithme V1 déterministe ## R. Algorithme V1 déterministe
@ -250,8 +302,15 @@ compatibles. L'utilisateur choisit et confirme.
- salle libre/occupée et chevauchement ; - salle libre/occupée et chevauchement ;
- planning manuel sans Pronote ; - planning manuel sans Pronote ;
- Pronote désactivé, indisponible, puis synchronisé ; - Pronote prend le relais sur un manuel initial non protégé ;
- conflit manuel/Pronote et suppression distante ; - Pronote modifie l'heure d'un manuel initial ;
- manuel protégé jamais écrasé ;
- conflit Pronote/réservation protégée ;
- Pronote désactivé ou indisponible ;
- retour de Pronote après plusieurs jours et fraîcheur affichée ;
- suppression côté Pronote et manuel initial supplanté ;
- absence de doublon manuel + Pronote ;
- rapprochement ambigu nécessitant validation humaine ;
- préventif de 45 minutes entre deux cours ; - préventif de 45 minutes entre deux cours ;
- durée/échéance d'une tâche liée à un lot ; - durée/échéance d'une tâche liée à un lot ;
- pause, horaires hors travail, fermeture et permanence ; - pause, horaires hors travail, fermeture et permanence ;
@ -265,12 +324,18 @@ compatibles. L'utilisateur choisit et confirme.
## U. Scénarios métier de validation ## U. Scénarios métier de validation
1. **Début d'année manuel** : saisir les créneaux de plusieurs salles, 1. **Rentrée progressive** : le gestionnaire saisit le planning manuellement,
planifier une maintenance et vérifier l'absence de Pronote. planifie immédiatement la maintenance, puis Pronote fonctionne quelques
2. **Mise à jour Pronote** : importer un nouveau cours, modifier un cours jours plus tard. Les cours compatibles reprennent les manuels initiaux,
importé et vérifier qu'un créneau manuel reste intact. sans doublon ; une réservation protégée reste intacte.
2. **Mise à jour Pronote** : importer un cours correspondant, modifier son
heure puis vérifier que seul le manuel initial non protégé est supplanté et
que l'historique reste lisible.
3. **Panne Pronote** : simuler une semaine sans connexion, conserver le 3. **Panne Pronote** : simuler une semaine sans connexion, conserver le
dernier planning et afficher sa date de fraîcheur. dernier planning Pronote connu et tous les manuels, avec date de fraîcheur.
4. **Retour de Pronote** : après plusieurs jours, synchroniser une nouvelle
version, traiter une suppression distante et demander validation pour un
rapprochement ambigu.
4. **Préventif interstitiel** : proposer 45 minutes entre deux cours en 4. **Préventif interstitiel** : proposer 45 minutes entre deux cours en
respectant la pause et le temps de travail. respectant la pause et le temps de travail.
5. **Tournée locale** : cinq préventifs de la même zone sont regroupés avant 5. **Tournée locale** : cinq préventifs de la même zone sont regroupés avant
@ -308,8 +373,9 @@ d'autres sources et statistiques de charge.
Avant développement, valider : Avant développement, valider :
1. `RoomSchedule` comme base des occupations internes et ses champs de 1. `RoomSchedule` comme base des occupations internes et ses champs de
provenance ; provenance/protection ;
2. la règle « Pronote ne modifie jamais le manuel » ; 2. la règle « Pronote peut supplanter le manuel initial non protégé, mais ne
modifie jamais le manuel protégé » ;
3. l'usage de `PlanningItem` comme projection anti-doublon ; 3. l'usage de `PlanningItem` comme projection anti-doublon ;
4. le périmètre des tâches administratives et des étapes entreprise ; 4. le périmètre des tâches administratives et des étapes entreprise ;
5. le niveau de priorité/contrainte exposé aux utilisateurs ; 5. le niveau de priorité/contrainte exposé aux utilisateurs ;
@ -317,3 +383,12 @@ Avant développement, valider :
7. la politique lorsque les horaires d'un agent sont absents ; 7. la politique lorsque les horaires d'un agent sont absents ;
8. la future association multi-techniciens ; 8. la future association multi-techniciens ;
9. les index et volumes acceptables pour la vue semaine/année. 9. les index et volumes acceptables pour la vue semaine/année.
### Décision recommandée
Retenir l'**option A** : `source = manual | pronote | import` complétée par
`protected_from_sync` et un statut historique (actif, supplanté, désactivé).
Elle évite de multiplier les valeurs de provenance tout en distinguant
clairement le manuel initial du manuel protégé. La vue interne résolue est la
seule entrée du moteur de maintenance ; Pronote ne devient jamais une
dépendance fonctionnelle.