# Moteur de planification interne — proposition d'architecture

**Statut : étude et spécification uniquement.** Aucun modèle, route,
migration, synchronisation ou changement applicatif n'est créé par ce
document.

## A. Architecture actuelle

Le dépôt possède déjà plusieurs briques réutilisables :

- `Room` et `RoomSchedule` (`app_new/core/models/college.py`) pour les salles
  et un emploi du temps hebdomadaire (`week_start`, jour, début/fin, matière,
  enseignant, classe) ;
- `WorkSchedule`, `WorkScheduleTemplate`, `TechnicianAvailability`,
  `PersonalLeave`, `Training`, `CollegeClosure`, `ClosureSchedule` et
  `ClosureWorkDay` (`app_new/core/models/planning.py`) pour horaires, pauses,
  absences, formations, fermetures et permanences ;
- `PreventiveTask` (durée, lot, équipement/catégorie, fenêtre autorisée),
  `LotTask` (durée, périodicité, sécurité) et `ScheduledTask` (date, début,
  fin, durée, salle, équipement, entreprise, contrat, technicien, statut) ;
- `Intervention` avec date planifiée, priorité, salle/équipement,
  technicien et entreprise ;
- `AdminTask`, actuellement récurrente avec heure et durée ;
- `PlanningDay`/`PlanningItem`, qui représentent déjà prévention, curatif,
  administratif et formation avec durée, salle, priorité et statut.

`planning_service.py` sait produire une journée et calculer des heures de
travail, mais `schedule_task` ne place pas réellement de créneau et la
disponibilité de salle n'est pas croisée avec toutes les tâches internes.
L'implémentation interroge en outre un symbole `PronoteSchedule` alors que le
modèle local de salle observé est `RoomSchedule` : cette dette doit être
résolue avant d'automatiser la disponibilité.

## B. Limites actuelles

1. `RoomSchedule` ne conserve pas de provenance, identifiant externe,
   validité ou récurrence explicite.
2. Le service de planification peut retomber sur 08:00–17:00 et certaines
   requêtes d'horaires/indisponibilités ne filtrent pas toujours par agent.
3. Les conflits salle, tâches, pauses, fermetures et interventions ne sont
   pas évalués dans un même calendrier.
4. `AdminTask` ne porte ni statut d'exécution, priorité, agent ou fenêtre.
5. `PlanningItem` et `ScheduledTask` se recouvrent partiellement, ce qui peut
   produire des doublons d'affichage.
6. L'import Pronote est optionnel dans les routes mais ne doit jamais être une
   dépendance du calcul interne.

## C. Principe de vérité interne

La GMAO possède des créneaux internes d'occupation. Chaque créneau conserve
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é.

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 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. 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

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

### 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

Le calcul croise les occupations internes, fermetures de salle/collège,
interventions, tâches planifiées, rendez-vous d'entreprises et permanence.
Les catégories d'occupation sont celles de la saisie manuelle. Une tâche de
45 minutes peut occuper 12:00–12:45 entre deux cours si le technicien est
également disponible ; les créneaux hors horaires, pause ou fermeture sont
exclus.

Le futur service doit utiliser `RoomSchedule` pour les cours importés ou
manuels et `ScheduledTask`/`PlanningItem`/`Intervention` pour les activités
internes, sans appeler Pronote en direct.

## H. Préventif et lots

`PreventiveTask` et `LotTask` fournissent la périodicité, la durée, la portée,
les restrictions d'accès et le caractère critique. `ScheduledTask` est la
source d'exécution d'un travail généré : lot, tâche, équipement/local, date
cible, durée, début/fin, priorité, statut, entreprise/contrat et technicien.

Les créneaux proposés doivent afficher **quoi, où, quand, durée** et refuser
une date dépassant une échéance de conformité. Une intervention convertie ne
doit pas être affichée deux fois.

## I. Horaires de travail

Le moteur utilise le `WorkSchedule` de l'agent, les templates, pauses,
`TechnicianAvailability`, congés, formations, fermetures et
`ClosureWorkDay`. Il ne planifie jamais sur la pause ou hors intervalle. La
V1 doit exiger un agent explicite pour une proposition automatique et ne pas
inventer 08:00–17:00 lorsque la configuration manque ; l'absence de données
doit être expliquée.

## J. Tâches administratives

Les tâches administratives sont des activités planifiables qui consomment du
temps : commande, devis, compte rendu, registre, dossier ou appels. La V1
doit compléter le modèle actuel par une durée, priorité, statut, fenêtre/date,
heure planifiée et responsable, ou projeter ces données dans
`PlanningItem`. Une récurrence ne doit pas masquer l'instance réellement
faite ou reportée.

## K. Entreprises extérieures

Un rendez-vous doit représenter au minimum entreprise, date/heure fixe,
intervention/contrat lié, durée d'accueil, accompagnement, éventuels passages
et temps administratif final. Pour la V1, réutiliser `ScheduledTask`/`PlanningItem`
avec `company_id` et un type d'étape simple (accueil, contrôle, signature)
plutôt que créer un système de calendrier externe. Une entité
`ExternalCompanyAppointment` séparée ne sera justifiée que si plusieurs
étapes doivent être suivies indépendamment.

## L. Urgences et contraintes

Les niveaux compréhensibles sont :

- **Fixe** : rendez-vous entreprise ou examen à heure imposée ;
- **Urgent** : fuite, sécurité ou panne critique ;
- **Échéance** : conformité ou tâche à terminer avant une date ;
- **Déplaçable** : prévention réalisable dans la fenêtre ;
- **Administratif** : travail planifiable avant une échéance.

Une urgence peut interrompre ou repousser, mais le système conserve chaque
tâche, son ancien créneau, sa raison et sa nouvelle proposition.

## M. Optimisation géographique

La première version calcule un coût ordinal simple : même salle (0), même
zone (1), même bâtiment (2), autre bâtiment (3). Ce coût ne passe jamais avant
sécurité, rendez-vous fixe, échéance, disponibilité, horaires et compétences.
Il sert uniquement à départager des créneaux équivalents, sans GPS ni tournée
complexe.

Ordre d'arbitrage : urgence/sécurité, rendez-vous fixe, échéance,
disponibilité salle, horaires, compétence/affectation, regroupement
géographique, réduction des trous.

## N. Replanification et contrôle humain

Lorsqu'une urgence arrive à 10:00, l'interface propose les tâches décalables,
les conserve avec leur historique et expose les créneaux alternatifs. Elle
classe chaque élément en conservé, décalé, non planifiable ou nouvelle
proposition. Une validation humaine est obligatoire ; aucun déplacement
silencieux.

Un déplacement manuel revalide conflit salle, horaires, pauses, autre tâche,
rendez-vous fixe et fermeture. Le message doit être métier : « Salle 101
occupée jusqu'à 11:00 », avec créneaux disponibles.

## O. Multi-techniciens futur

La colonne actuelle `assigned_to_id` convient à la V1 mono-agent, mais ne doit
pas devenir une contrainte conceptuelle. Une évolution pourra ajouter une
association `PlannedTaskAssignee` (tâche, utilisateur, rôle), avec
`preferred_technician`, `required_skill` et `required_people_count`. Une
personne préférée n'est pas une personne assignée ; une compétence requise
est une contrainte, pas une préférence. Deux personnes seront représentées
par deux affectations liées à la même activité.

## P. Modèle de données recommandé

Réutiliser au maximum les tables présentes :

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.
3. **Utiliser `PlanningItem` comme projection de placement** (date, début,
   fin, durée, salle, statut, priorité) et imposer une règle anti-doublon avec
   les références `scheduled_task_id`/`intervention_id`/`admin_task_id`.
4. **Étendre `AdminTask` ou sa projection** pour responsable, statut,
   priorité et fenêtre, sans perdre la récurrence.
5. **Rendez-vous entreprise** : d'abord `ScheduledTask`/`PlanningItem` avec
   `company_id`; créer `ExternalCompanyAppointment` seulement si les étapes
   deviennent des objets suivis.

Chaque futur index doit couvrir les requêtes par date/statut, salle/intervalle
et agent. Aucune table de planning parallèle ne doit être créée sans preuve
que `PlanningItem` ne peut pas être consolidé.

## Q. Migrations futures

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

1. Construire les intervalles de travail de l'agent, en retirant pauses,
   congés, formations, fermetures et permanences incompatibles.
2. Construire les intervalles occupés de la salle et du planning interne.
3. Générer les créneaux possibles par durée et fenêtre.
4. Éliminer conflits, hors horaires, échéances et rendez-vous fixes.
5. Classer par contrainte puis coût géographique, puis heure la plus proche.
6. Retourner une proposition explicable, sans commit automatique.

Une heuristique simple est préférable à un solveur d'optimisation complet :
elle est déterministe, testable, lisible et réversible.

## S. Interface proposée

### Vue journalière

Afficher pour chaque bloc : heure, bâtiment/zone/salle, titre, catégorie
(préventif, curatif, urgent, administratif, entreprise), durée, statut et
contrainte. Un panneau « Propositions » explique les créneaux refusés.

### Gestion des salles

Calendrier par salle/semaine, saisie manuelle claire, provenance visible et
indication « données Pronote anciennes » sans bloquer le planning interne.

### Conflits

Afficher la cause, la ressource concernée et deux ou trois créneaux
compatibles. L'utilisateur choisit et confirme.

## T. Tests à prévoir

- salle libre/occupée et chevauchement ;
- planning manuel sans Pronote ;
- 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 ;
- tâche administrative récurrente, réalisée et reportée ;
- entreprise avec accueil, accompagnement et signature ;
- urgence décalant deux tâches sans perte d'historique ;
- regroupement même salle/zone/bâtiment ;
- déplacement manuel vers rendez-vous fixe ;
- données sans agent/horaires explicites ;
- futur multi-techniciens compatible (test de structure, pas d'optimiseur).

## U. Scénarios métier de validation

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 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
   un autre bâtiment.
6. **Entreprise fixe** : rendez-vous chaudière à 09:00, accueil, contrôle,
   signature et temps administratif visibles.
7. **Urgence** : fuite à 10:00, conserver le rendez-vous fixe, décaler deux
   tâches déplaçables et faire valider la proposition.
8. **Journée mixte** : maintenance, commande, entreprise et intervention
   curative avec horaires et statuts cohérents.
9. **Fermeture/permanence** : proposer une tâche pendant une journée rouverte
   de vacances, refuser les dates fermées.
10. **Salle indisponible** : fermeture d'une salle, replanification vers une
    autre salle compatible sans modifier silencieusement l'objet métier.

## V. MVP obligatoire

- planning interne des salles, saisie manuelle et provenance ;
- Pronote optionnel, import limité à ses propres créneaux ;
- durée, début/fin, statut et priorité des tâches ;
- respect horaires, pauses, fermetures et absences ;
- prévention/lot, interventions, tâches administratives ;
- rendez-vous entreprise simple ;
- urgences et propositions de replanification ;
- regroupement salle/zone/bâtiment ;
- déplacement manuel avec avertissements ;
- vue journalière lisible et déterministe.

## W. Évolutions futures et décisions à valider

**Plus tard :** multi-techniciens avancé, compétences, absences détaillées,
deux personnes par tâche, optimisation complexe, calcul de trajets, import
d'autres sources et statistiques de charge.

Avant développement, valider :

1. `RoomSchedule` comme base des occupations internes et ses champs de
   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 ;
6. le comportement d'une donnée Pronote obsolète ;
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.
