# Décisions architecturales 0.2.0-dev.3

Les décisions sont ajoutées au fil des checkpoints. Les modèles existants sont privilégiés avant toute nouvelle table.

## D-005 — Centre de configuration permanent et setup progressif

**CONTEXTE** : le setup initial ne doit pas devenir une page jetable ; une GMAO utile doit accepter une configuration partielle et expliciter les actions restantes.

**OPTIONS ÉTUDIÉES** : checklist statique de fin d'installation ; tableau calculé et permanent avec liens d'action.

**CHOIX** : créer/faire évoluer un Centre de configuration permanent, organisé par domaines (Essentiel, Maintenance, Patrimoine, Entreprises, Compteurs, Cartographie technique, Intégrations), avec états calculés et intégrations facultatives non bloquantes.

**JUSTIFICATION** : rendre la configuration progressive observable et exploitable sans SQL ni connaissance de l'architecture.

**CONSÉQUENCES** : chaque contrôle doit avoir une règle de calcul et une destination UI ; Pronote, ENT, Outlook, Yeastar, IA, compteurs, logements et cartographie restent facultatifs.

**DATE/COMMIT** : 2026-08-23 / préparation checkpoint B.

## D-006 — RoomType structurel, RoomProfile séparé à étudier

**CONTEXTE** : `RoomType` classe actuellement les locaux et est utilisé par Pronote et les écrans de patrimoine, mais ne porte aucun catalogue d'équipements, de quantités ou d'ouvrages.

**OPTIONS ÉTUDIÉES** : enrichir `RoomType` ; créer un `RoomProfile` distinct relié à `RoomType` ; stocker des JSON dans `RoomType`.

**CHOIX** : `RoomProfile` séparé, avec `RoomProfileItem` et `RoomSurface` normalisés. `RoomType` reste structurel et n'est pas modifié.

**JUSTIFICATION** : éviter de casser les usages de classification/enseignement et permettre plusieurs profils de proposition pour un même type structurel.

**CONSÉQUENCES** : migrations `k6f7a8b9c0d1` et `l7a8b9c0d1e2` ajoutées ; association de profil nullable ; aucune création automatique d'équipement sans validation utilisateur.

**DATE/COMMIT** : 2026-08-23 / implémentation checkpoint B en cours.

## D-007 — Origine d’une proposition de profil

**CONTEXTE** : une réapplication d’un RoomProfile doit ajouter uniquement le delta sans rechercher les équipements par nom.

**OPTIONS ÉTUDIÉES** : comparaison fragile nom/catégorie ; table de liaison ; identifiant nullable sur Equipment.

**CHOIX** : ajouter `Equipment.room_profile_item_id`, nullable, indexé et relié à `RoomProfileItem`. Les équipements historiques sans origine restent valides.

**JUSTIFICATION** : l’identifiant de proposition est stable, compatible avec les noms en doublon et permet une idempotence explicite sans mutation silencieuse.

**CONSÉQUENCES** : migration non destructive `m8b9c0d1e2f3`; le service commun renseigne l’origine lors de l’application d’un profil.

**DATE/COMMIT** : 2026-08-23 / checkpoint B.

## D-008 — Centre de configuration sans faux état d’erreur

**CONTEXTE** : le lot d’un équipement est facultatif et les modèles de contrats exposent des colonnes ORM dont certaines propriétés métier ne sont pas directement filtrables.

**CHOIX** : afficher un équipement sans lot comme information facultative ; filtrer les contrats via `Contract.__table__.c` pour éviter les erreurs de rendu du Centre.

**JUSTIFICATION** : une configuration progressive ne doit pas bloquer l’inventaire ni produire une page de synthèse cassée.

**CONSÉQUENCES** : les liens du nouveau menu pointent vers Centre, profils, bulk et horaires ; les intégrations avancées restent facultatives.

**DATE/COMMIT** : 2026-08-23 / validation B.

## D-000 — Checkpoints indépendants

**CONTEXTE** : le chantier couvre plusieurs domaines avec migrations et risques différents.

**OPTIONS ÉTUDIÉES** : un gros développement continu ; checkpoints commitables.

**CHOIX** : checkpoints A à E, chacun documenté, testé et versionné.

**JUSTIFICATION** : reprise fiable, absence de régression masquée, dépôt source de vérité.

**CONSÉQUENCES** : aucun checkpoint suivant ne démarre avant validation du précédent.

**DATE/COMMIT** : 2026-08-23 / initialisation.

## D-001 — Service de création Equipment

**CONTEXTE** : les routes fiche avancée et wizard construisaient des objets différents et le wizard retrouvait ensuite des équipements par nom.

**OPTIONS ÉTUDIÉES** : conserver les routes indépendantes ; centraliser la construction dans un service.

**CHOIX** : `core.services.equipment_creation.create_equipment`, lot et catégorie facultatifs, retour de l'instance persistée.

**JUSTIFICATION** : évite les collisions de noms et garantit la même gestion de quantité, parent, localisation et génération préventive.

**CONSÉQUENCES** : les anciens parcours sont progressivement migrés ; les relations techniques restent hors `parent_id`.

**DATE/COMMIT** : 2026-08-23 / checkpoint A en cours.

## D-017 — Compléments C4

Le rôle contractuel d'un compteur est explicite (`COPIER_BW`, `COPIER_COLOR` ou `NONE`) : un compteur inconnu n'est jamais classé silencieusement N&B. Lorsqu'il manque l'index de début d'une période, la consommation manquante est estimée par moyenne journalière historique ; pour les équipements d'impression, les jours scolaires comparables sont privilégiés. Sans historique suffisant, la qualité est `PARTIAL`. L'agrégation globale suit `PARTIAL > ESTIMATED > EXACT`.

La clôture d'un contrat est centralisée dans un service métier : la période en cours est tronquée, les périodes futures jamais commencées sont neutralisées et les associations ouvertes sont fermées, sans suppression de l'historique. Une commande partiellement reçue peut être annulée pour son restant ; le stock déjà reçu est conservé, et plusieurs commandes ouvertes sont agrégées.

La couverture de stock est une estimation globale fondée sur les rythmes des équipements actifs compatibles, avec qualité `ESTIMATED`, `PARTIAL` ou indisponible. Les alertes quota et stock ouvertes remontent dans « Ma journée » comme informations sans durée ni intervention automatique, filtrées par `contract.view` et `stock.view`. La migration additive `t5h6i7j8k9l0` complète `s4g5h6i7j8k9`.

## D-017 — Contrats photocopieurs et consommables C4

Les photocopieurs et imprimantes restent des `Equipment` et leurs compteurs restent des `Meter`. Les contrats sont pluriannuels ; la date globale du contrat est distincte de la date de début des périodes de quota et de l'anniversaire configurable. Les quotas N&B/couleur appartiennent à la période contractuelle, jamais à une machine.

Le parc est historisé par une association datée contrat/machine. Un remplacement ferme l'association précédente avec un motif et conserve les compteurs/relevés de l'ancienne machine. Les projections utilisent prioritairement les jours scolaires connus ; une insuffisance de données est affichée comme telle. Les alertes de dépassement sont dédupliquées par période et couleur.

Les références `Consumable` existantes sont le stock global partagé. `EquipmentConsumable` porte la compatibilité multi-machines, `ConsumableUsage` porte les sorties, et les nouvelles commandes/réceptions augmentent le stock uniquement lors d'une réception réelle. La durée est calculée séparément pour chaque couple équipement/référence ; l'installation courante n'entre pas dans la moyenne terminée.

**MIGRATION** : `s4g5h6i7j8k9` est additive depuis `r3f4g5h6i7j8` et conserve les consommables historiques. La validation base vide et la validation visuelle restent ouvertes.

**PERFORMANCE** : le calcul contractuel charge les compteurs et relevés en lot ; le détail contrat précharge fournisseur, périodes et parc. Sur le jeu `TEST_UI_C4_PERF_*`, le calcul est passé de 75 à 11 requêtes et le détail de 234 à 98 requêtes. Le détail consommable mesuré à 4 requêtes reste séparé.

## D-011 — Échéances et tournées de relevés C2

**CONTEXTE** : un relevé est un travail opérationnel planifiable, mais ne doit pas être modélisé comme une intervention. Les fréquences simples ne suffisent pas pour les dates contractuelles et les logements.

**CHOIX** : créer `MeterReadingSchedule` pour la règle, `MeterReadingOccurrence` pour chaque échéance logique, `MeterReadingRound`/`MeterReadingRoundMember` pour la définition d'une tournée et `MeterReadingRoundOccurrence` pour sa réalisation datée. L'unicité `(schedule_id, target_date)` et l'unicité d'une occurrence de tournée par date rendent la génération idempotente.

La date cible reste distincte de la date opérationnelle. L'anticipation réutilise le moteur horaire/calendrier existant pour les règles scolaires ; les logements sont hors calendrier scolaire par défaut. Sans horaires explicites, le système conserve la date cible plutôt que d'inventer un jour travaillé.

**TRAITEMENT** : une occurrence avec relevé appelle le service C1 `record_meter_reading`. Une occurrence sans relevé conserve un motif distinguant notamment inaccessible, absent, refus et relevé non connu, sans créer de mesure artificielle. Une occurrence ouverte d'un compteur remplacé est annulée immédiatement dans la transaction de remplacement avec une raison système et reste historisée.

**INTÉGRATION** : les occurrences sont une source du `DayPlanner`, avec responsable, durée et retard calculé ; elles ne créent pas d'intervention et ne simulent pas de créneau si le planificateur ne sait pas en attribuer un. Les routes de configuration sont administrateur ; le traitement d'une occurrence est limité à l'utilisateur assigné ou à un administrateur.

**LIMITES C2** : génération à horizon borné autour de « Ma journée » (365 jours de regard arrière et 31 jours d'avance) ; aucune analyse de consommation, conversion, alerte, coût, contrat ou quota n'est implémentée.

**DATE/COMMIT** : 2026-08-24 / checkpoint C2.

## D-012 — Intervalles et contexte analytique C3

**CHOIX** : calculer les intervalles à la demande à partir de deux relevés réels successifs, conserver les mesures brutes et annoter chaque intervalle par sa composition calendaire et ses régimes de chauffage. Les logements ne sont pas reclassés selon les vacances scolaires. Les régimes sont datés par compteur et réseau pour éviter l'hypothèse d'un chauffage global unique.

**JUSTIFICATION** : éviter une table de cache difficile à invalider après correction de relevé, tout en gardant des requêtes explicites et des données historiques intactes. Une rupture de compteur ou un reset ne produit jamais de delta analytique trompeur.

## D-013 — Alertes, gaz et coûts C3

**CHOIX** : persister les règles et alertes C3 séparément des seuils historiques ; conserver une évidence par intervalle, dédupliquer tant qu'une alerte équivalente est ouverte, utiliser une médiane avec minimum configurable (défaut 3), puis permettre un lien relationnel vers une intervention. Les coefficients gaz sont datés et manuels prioritaires sur ceux issus de facture ; le tarif applicable est celui de la date du relevé final et le coût reste informatif.

**HORS PÉRIMÈTRE** : aucune consommation avancée, contrat/quota photocopieur, moteur comptable ou C4.

## D-014 — Compléments C3

Les périodicités DAY/WEEK/MONTH réutilisent le même agrégateur. Les métriques contextuelles répartissent proportionnellement la quantité selon le nombre de jours du contexte et sont présentées comme analytiques, jamais comme une mesure physique directe. La médiane et le seuil d’écart configurable (défaut 30 %) sont réservés à l’anomalie statistique ; l’estimation d’un sous-compteur utilise la moyenne journalière des derniers intervalles fiables. L’eau en m³ ne déclenche jamais la conversion gaz : seul `meter_type == "gaz"` peut utiliser un coefficient.

Les preuves d’alerte sont uniques par alerte et intervalle. Les conclusions `CONFIRMED_LEAK` et `READING_ERROR` excluent leurs preuves de la baseline ; `BAD_THRESHOLD` et `NORMAL_EXPLAINED` ne les excluent pas automatiquement. Les régimes chauffage restent liés au compteur avec `network_name` : cette solution permet plusieurs réseaux sans duplication obligatoire d’une nouvelle entité, et reste remplaçable par une alimentation GTB future.

## D-016 — Type de règle statistique C3

`MeterAlertRule.rule_type` distingue explicitement `MANUAL_THRESHOLD` et `STATISTICAL_ANOMALY`. Les règles historiques et les nouvelles règles sans type explicite restent manuelles par défaut. Le service de relevé appelle l'évaluation manuelle ou la médiane statistique selon ce type, en réutilisant `min_comparable_intervals` et `min_deviation_percent`. Une correction réévalue uniquement les intervalles voisins et conserve l'idempotence des preuves. La colonne est ajoutée par la migration additive `r3f4g5h6i7j8`, sans modifier les migrations C3 déjà appliquées.

## D-015 — Clôture C3 : recalcul ciblé et exposition opérationnelle

Les nouveaux relevés déclenchent un recalcul limité aux intervalles voisins ; une correction ne réévalue que les segments précédent et suivant. Les preuves ouvertes dépendant d'une valeur corrigée sont neutralisées et l'alerte est clôturée si elle ne possède plus de preuve valide ; les alertes déjà clôturées restent historiques. Le recalcul est appelé dans la transaction du service de relevé, sans worker externe ni rescannage complet à chaque GET.

La route de clôture d'alerte réutilise le RBAC `planning.manage`. « Ma journée » ne montre que les alertes ouvertes liées à un compteur attribué au compte ou à une occurrence qui lui est attribuée ; l'administrateur conserve la vision globale. Le dashboard utilise une fenêtre explicite de 30 jours et agrège les unités séparément. Les mesures C3-CLOSE instrumentées sur le jeu de performance donnent notamment 83 requêtes dashboard, 24 fiche analytique, 4 évaluation d'alerte et 86 Ma journée ; les temps restent dépendants de l'environnement MariaDB.

## D-009 — Rattachement explicite des compteurs

**CONTEXTE** : les compteurs historiques étaient obligatoirement rattachés à `Equipment`, alors que l'exploitation nécessite aussi des compteurs de bâtiment, zone, local et logement.

**OPTIONS ÉTUDIÉES** : cible polymorphe `scope_type/scope_id` ; FK nullable distinctes ; création d'un faux équipement pour chaque compteur patrimoine.

**CHOIX** : conserver `equipment_id` nullable et ajouter `building_id`, `zone_id`, `room_id` et `housing_unit_id`, avec une contrainte SQL imposant exactement un rattachement principal. `Room` est supporté directement.

**JUSTIFICATION** : les FK garantissent l'intégrité référentielle MariaDB, rendent les requêtes explicites et évitent le faux équipement. Le polymorphisme aurait supprimé les FK natives et complexifié les jointures.

**CONSÉQUENCES** : l'index historique des compteurs Equipment reste valide ; les listes ne font plus de `join(Equipment)` obligatoire ; migration non destructive `n9c0d1e2f3g4`.

## D-010 — Hiérarchie, relevés audités et remplacement

**CHOIX** : `Meter.parent_id` porte une hiérarchie indépendante d'`Equipment.parent_id`, avec plusieurs racines possibles et validation des cycles. `MeterReading` reste une mesure physique dans l'unité native. `record_meter_reading` est le service unique ; une baisse exige un reset explicite et justifié. Une correction met à jour la mesure tout en conservant ancienne valeur, nouvelle valeur, auteur, date et justification dans `MeterReadingCorrection`. Un remplacement crée une nouvelle identité reliée par `replaces_meter_id` et clôt l'ancien compteur.

**PHOTOS** : une photo facultative est stockée sur le relevé (`photo_filename`, `photo_path`) en réutilisant les extensions et la racine d'upload existantes ; aucune association artificielle à `EquipmentDocument`.

**HORS C1** : échéances, consommations, coefficients gaz, alertes, coûts, contrats et quotas restent respectivement C2, C3 et C4.

**DATE/COMMIT** : 2026-08-23 / checkpoint C1.

## D-003 — Catégorie et lot facultatifs

**CONTEXTE** : un inventaire peut être saisi avant que le référentiel de maintenance soit complet.

**OPTIONS ÉTUDIÉES** : bloquer toute création sans lot ; accepter un équipement incomplet et le compléter plus tard.

**CHOIX** : la création reste valide sans lot (et sans catégorie dans les parcours wizard) ; un lot fourni est validé et peut déclencher la génération préventive.

**JUSTIFICATION** : respecter le principe de configuration progressive sans fabriquer de maintenance implicite.

**CONSÉQUENCES** : l'interface doit pouvoir signaler ultérieurement « Lot non renseigné » ; les relations techniques restent indépendantes.

**DATE/COMMIT** : 2026-08-23 / checkpoint A.

## D-004 — Versionnement du checkpoint A

**CONTEXTE** : le chantier interdit `0.1.0-dev.13` et demande des versions correspondant à des checkpoints réellement validés.

**CHOIX** : le checkpoint A validé passe de `0.1.0-dev.12` à `0.2.0-dev.1` ; B, C, D et E restent séparés.

**JUSTIFICATION** : la création Equipment est consolidée et testée sans nouvelle régression ; les erreurs restantes sont exactement la baseline historique.

**CONSÉQUENCES** : Docker doit être reconstruit avec la nouvelle version ; aucune migration n'est ajoutée au checkpoint A.

**DATE/COMMIT** : 2026-08-23 / version commit.

## D-002 — Durée d'intervention inconnue

**CONTEXTE** : `Intervention.effective_estimated_duration` retournait silencieusement 60 minutes.

**CHOIX** : retourner 0 si aucune estimation ou moyenne historique n'existe ; DayPlanner affiche alors une tâche sans durée à renseigner.

**JUSTIFICATION** : ne pas présenter une estimation arbitraire comme une contrainte métier.

**CONSÉQUENCES** : les statistiques historiques restent inchangées ; les workflows doivent renseigner les interventions planifiables.

**DATE/COMMIT** : 2026-08-23 / checkpoint A en cours.
