# Modèle de données Tout le planning tient dans un seul fichier JSON, par défaut `data/projets.json`. Le fichier est écrit indenté sur 2 espaces, avec les clés dans un ordre stable, pour rester lisible dans un diff git et éditable à la main. ## Structure ```json { "version": 2, "projects": [ { "id": "site-web", "name": "Refonte du site web", "color": "#3b82f6", "collapsed": false, "hidden": false, "baselineDate": "2026-06-12", "phases": [ { "id": "cadrage", "name": "Cadrage", "start": "2026-08-01", "end": "2026-08-20", "status": "done", "milestone": false, "notes": "Ateliers avec les trois pôles.", "baseline": { "start": "2026-07-25", "end": "2026-08-10" } }, { "id": "livraison", "name": "Mise en ligne", "start": "2026-11-02", "end": "2026-11-02", "status": "todo", "milestone": true, "notes": "" } ] } ] } ``` ## Racine | Champ | Type | Description | |---|---|---| | `version` | entier | Version du format. Vaut `2`. Sert à détecter un fichier trop ancien au chargement. | | `projects` | tableau | Les projets, dans l'ordre d'affichage des couloirs. | ## Projet | Champ | Type | Description | |---|---|---| | `id` | chaîne | Identifiant parlant, dérivé du nom (`Refonte du site web` → `site-web`). Unique dans le fichier. | | `name` | chaîne | Nom affiché. Non vide. | | `color` | chaîne | Couleur du couloir, en hexadécimal `#rrggbb`. | | `collapsed` | booléen | `true` si le couloir est plié. | | `hidden` | booléen | `true` si le projet est masqué de la frise. Ses données restent intactes. | | `baselineDate` | chaîne ou absent | Date à laquelle la référence a été figée. Absent si elle ne l'a jamais été. | | `phases` | tableau | Les phases, triées par date de début croissante. | ## Phase | Champ | Type | Description | |---|---|---| | `id` | chaîne | Identifiant parlant, unique **au sein de son projet**. | | `name` | chaîne | Nom affiché. Non vide. | | `start` | chaîne | Date de début, `AAAA-MM-JJ`. | | `end` | chaîne | Date de fin **incluse**, `AAAA-MM-JJ`. | | `status` | chaîne | `todo`, `doing`, `done` ou `blocked`. | | `milestone` | booléen | `true` pour un jalon. | | `notes` | chaîne | Texte libre, éventuellement vide. | | `baseline` | objet ou absent | Dates de référence : `{ "start": …, "end": … }`. Absent tant que la référence n'a pas été figée. | ## Règles de validation Appliquées par `js/model.js` et couvertes par `tests/model.test.js`. - `end` est postérieure ou égale à `start`. - Un jalon (`milestone: true`) a nécessairement `start === end`. Cocher « jalon » sur une phase de plusieurs jours ramène `end` sur `start`. - Les dates suivent strictement `AAAA-MM-JJ` et doivent exister réellement dans le calendrier (`2026-02-30` est rejetée). - `id` de projet unique dans le fichier ; `id` de phase unique dans son projet. - Un `id` est engendré à partir du nom : minuscules, accents retirés, tout ce qui n'est ni lettre ni chiffre remplacé par un tiret. En cas de collision, un suffixe numérique est ajouté (`cadrage-2`). Un `id` ne change jamais si le nom est modifié ensuite — il identifie, il ne décrit pas. - `status` fait partie des quatre valeurs autorisées ; toute autre valeur est ramenée à `todo`. - `color` est un hexadécimal `#rrggbb` valide. Un fichier invalide n'est jamais réparé en silence : le chargement échoue avec un message qui pointe le projet et la phase fautifs, pour que le fichier puisse être corrigé à la main. ## Dates : conventions Les dates sont manipulées comme des **chaînes `AAAA-MM-JJ`**, pas comme des objets `Date`. Cela évite tous les pièges de fuseau horaire : `new Date("2026-08-01")` est interprétée en UTC alors que `new Date(2026, 7, 1)` l'est en heure locale, ce qui décale d'un jour selon le fuseau. Les conversions n'ont lieu que dans les helpers de calcul de `model.js`. `end` est **incluse** : une phase du `2026-08-01` au `2026-08-01` dure un jour. Tout se compte en **jours calendaires**, week-ends et jours fériés compris. Les numéros de semaine affichés dans l'en-tête suivent l'**ISO 8601** : la semaine appartient à l'année où tombe son jeudi. `semaineISO()` renvoie donc le numéro *et* l'année correspondante, qui peut différer de celle de la date. ## Bornes et dérive - Les **bornes d'un projet** vont de la plus petite `start` à la plus grande `end` de ses phases. Un projet sans phase n'a pas de bornes et s'affiche comme un couloir vide. - **Figer la référence** copie, pour chaque phase du projet, ses `start` et `end` courantes dans son champ `baseline`, et inscrit la date du jour dans `baselineDate`. L'opération est rejouable : la refiger après un arbitrage assumé repart d'une base propre. - La **dérive** d'un projet est l'écart, en jours, entre la plus grande `end` actuelle et la plus grande `end` de référence. Elle est affichée arrondie en semaines. Une phase créée après le figeage n'a pas de `baseline` et ne compte pas dans la référence, mais compte dans les dates actuelles : ajouter une phase en fin de projet crée donc bien une dérive, ce qui est le comportement voulu.