Second Brain documentation

ADR 0108 – Santé d'un projet

Accepté (2026-10-17, vague 3 du document d'idées)

← Toutes les décisions

ADR 0108 – Santé d’un projet

Statut : Accepté (2026-10-17, vague 3 du document d’idées)

Un projet actif peut s’enliser sans que rien ne le dise : des tâches en retard s’accumulent, plus personne n’y touche. On veut une pastille, sur la fiche et dans l’arbre, qui le signale d’un coup d’œil. Les seuils sont des choix de l’utilisateur : ils vivent dans config/, pas dans le code.

Décision

Configuration : clé health d’un aspect

# config/aspects/projet.yaml
health:
  from: tache.projet          # champ référence des éléments rattachés au projet
  when: "projet.statut == 'actif'"   # facultatif : quels nœuds ont une santé (défaut : tous ceux qui portent l'aspect)
  late: "tache.statut != 'fait' && tache.statut != 'abandonne' && tache.statut != 'plus-tard' && tache.echeance < today"
  levels:                     # du plus grave au moins grave ; le premier niveau dont un seuil est atteint l'emporte
    - { level: risque, lateAtLeast: 3, idleDaysAtLeast: 30 }
    - { level: attention, lateAtLeast: 1, idleDaysAtLeast: 14 }
  • late et when sont écrits dans le langage des vues (ADR 0028) : late sur les champs de l’aspect de from, when sur ceux de l’aspect qui porte la clé. Aucun nom d’aspect, de champ, d’état ni de seuil dans le code.
  • Niveaux : attention (affiché « À surveiller ») et risque (« À risque ») ; sans seuil atteint, la santé est bon. Un niveau atteint si l’un de ses seuils l’est : lateAtLeast tâches en retard ou idleDaysAtLeast jours sans modification. Au moins un seuil par niveau.
  • Contrôles au chargement (DefinitionLoader.checkHealth, méta-schéma) : from est un champ référence d’un aspect chargé, late et when passent le contrôleur d’expressions, niveaux distincts avec seuils décroissants du plus grave au moins grave. Un aspect invalide est ignoré et signalé par /api/definitions/status.

Calcul côté serveur : HealthService

  • Retards : les éléments hors corbeille et hors archives dont from désigne le projet et qui satisfont late, comptés en SQL par le moteur de vues (ViewSql), regroupés par projet.
  • Jours sans modification : depuis la plus récente de updated_at du projet et de celles de ses éléments rattachés (une tâche terminée récemment compte comme une activité).
  • Deux requêtes par aspect qui déclare une santé, quel que soit le nombre de nœuds : une pour l’éligibilité (aspect porté et when), une pour les éléments. Le niveau est ensuite choisi en Java (HealthService.level, fonction pure testée).
  • Exposé dans NodeView.health et TreeItem.health ({ aspect, level, late, idleDays }, absent sans santé) : la fiche et chaque niveau de l’arbre l’obtiennent dans la réponse existante. Contrat OpenAPI et client régénérés.
  • Calculée à la lecture, jamais stockée : aucun événement, aucune migration.

Interface

  • Arbre : un rond (app-health-dot) à droite du titre. Le niveau passe aussi par la forme (carré tourné pour « à risque ») et par un texte pour les lecteurs d’écran et l’info-bulle : la couleur ne porte pas seule l’information.
  • Fiche : sous le titre, « À surveiller · 2 tâches en retard · 9 j sans modification ».

Alternatives écartées

  • Colonne health mise à jour par des événements : un second état à garder cohérent (retard qui apparaît avec le temps sans écriture) pour un calcul peu coûteux.
  • Champ calculé du langage des vues (projet.sante, comme nextAction) : utile pour filtrer, mais plus lourd (SQL corrélé par ligne) ; les vues peuvent déjà exprimer « projets avec tâches en retard » autrement. À reprendre si on veut filtrer par santé.
  • Seuils dans application.yml : le domaine vit dans config/ (règle du projet), et le projet est l’aspect qui sait ce qu’est un retard.

Limites connues

  • Le temps sans modification ignore le journal de suivi, les liens et les fichiers du projet (seules les modifications du nœud et de ses éléments comptent).
  • Les sous-projets ne comptent pas dans la santé du parent (un seul champ de rattachement, from).
  • La santé n’est pas encore filtrable dans les vues.