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
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
conceptuels sont `source`, `external_id`, `last_synced_at`,
`source_updated_at`, période de validité et statut actif. Le métier parle de
créneau d'occupation interne ; il ne dépend jamais d'une réponse en direct de
Pronote.
une provenance (`manual`, `pronote`, `import`) et, conceptuellement, un
booléen `protected_from_sync`, un `external_id`, les horodatages de
synchronisation et une période de validité.
L'application doit démarrer et rester pleinement utilisable avec
`PRONOTE_ENABLED=false`, sans token et sans synchronisation.
La distinction métier est la suivante :
- **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
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,
examen, réservation, travaux, intervention, fermeture ou autre. Les règles de
validation refusent début ≥ fin et signalent les chevauchements. Une saisie
manuelle est toujours conservée, même lorsqu'une source externe est absente.
examen, réservation, travaux, intervention, fermeture ou autre. Par défaut,
une saisie de cours destinée à l'initialisation est **non protégée**. Pour une
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
Pronote peut fournir des créneaux après configuration, mais ne possède jamais
la source interne de vérité. La synchronisation ne doit lire/écrire que les
créneaux qu'elle a créés (`source=pronote`, `external_id` connu).
Lorsque Pronote devient disponible et fiable, il peut prendre progressivement
le relais sur les créneaux manuels **non protégés** correspondant à son emploi
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
- Créneau manuel : jamais modifié ni supprimé automatiquement par Pronote.
- Créneau Pronote identique : mise à jour de ses champs et de
`last_synced_at`.
- Créneau supprimé côté Pronote : désactivation du seul créneau Pronote
correspondant ; aucune suppression manuelle.
- Collision manuel/Pronote : le manuel gagne, le conflit est signalé à
l'utilisateur avec les deux provenances.
- Pronote indisponible : conserver les dernières données importées, marquer
leur fraîcheur et afficher un avertissement ; ne jamais vider le planning.
- Donnée Pronote obsolète : elle peut être désactivée selon une politique de
rétention explicite, jamais effacée silencieusement.
### Rapprochement déterministe
Un créneau Pronote peut être rapproché automatiquement d'un manuel initial
uniquement si les critères stables concordent : même salle, même période de
validité, même jour/date et horaires identiques ou suffisamment proches,
complétés si disponibles par classe/groupe, matière et enseignant. La V1 ne
déduit pas une correspondance à partir d'un seul critère. Si la correspondance
est ambiguë, elle conserve les deux lignes, signale un conflit et demande une
validation humaine.
### 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
@ -188,9 +236,10 @@ par deux affectations liées à la même activité.
Réutiliser au maximum les tables présentes :
1. **Étendre `RoomSchedule`** : `source`, `external_id`, horodatage de
synchronisation, validité, type/libellé et statut actif. Index futur sur
`(room_id, week_start, day_of_week, start_time, end_time, source)`.
1. **Étendre `RoomSchedule`** : `source`, `protected_from_sync`,
`external_id`, horodatage de synchronisation, validité, type/libellé,
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
ultérieurement contrainte de priorité et fenêtre si les champs existants ne
suffisent pas.
@ -209,11 +258,14 @@ que `PlanningItem` ne peut pas être consolidé.
## Q. Migrations futures
Les extensions ci-dessus nécessiteront des migrations Alembic non destructives,
avec valeurs par défaut (`source=manual`, statut actif) et backfill explicite
des `RoomSchedule` existants. Les données Pronote historiques ne doivent pas
être confondues avec le manuel : si leur origine est inconnue, les marquer
`import` et demander validation.
Une migration non destructive ajoutera à `RoomSchedule` la provenance, la
protection, les identifiants/horodatages de synchronisation, la validité et le
statut. Les lignes existantes seront initialisées avec `source=manual` et
`protected_from_sync=false`, puis un écran de validation permettra de protéger
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
@ -250,8 +302,15 @@ compatibles. L'utilisateur choisit et confirme.
- salle libre/occupée et chevauchement ;
- planning manuel sans Pronote ;
- Pronote désactivé, indisponible, puis synchronisé ;
- conflit manuel/Pronote et suppression distante ;
- Pronote prend le relais sur un manuel initial non protégé ;
- 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 ;
- durée/échéance d'une tâche liée à un lot ;
- pause, horaires hors travail, fermeture et permanence ;
@ -265,12 +324,18 @@ compatibles. L'utilisateur choisit et confirme.
## U. Scénarios métier de validation
1. **Début d'année manuel** : saisir les créneaux de plusieurs salles,
planifier une maintenance et vérifier l'absence de Pronote.
2. **Mise à jour Pronote** : importer un nouveau cours, modifier un cours
importé et vérifier qu'un créneau manuel reste intact.
1. **Rentrée progressive** : le gestionnaire saisit le planning manuellement,
planifie immédiatement la maintenance, puis Pronote fonctionne quelques
jours plus tard. Les cours compatibles reprennent les manuels initiaux,
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
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
respectant la pause et le temps de travail.
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 :
1. `RoomSchedule` comme base des occupations internes et ses champs de
provenance ;
2. la règle « Pronote ne modifie jamais le manuel » ;
provenance/protection ;
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 ;
4. le périmètre des tâches administratives et des étapes entreprise ;
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 ;
8. la future association multi-techniciens ;
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.