Skip to content

Données statistiques d'établissement (capacité, chambres, ETP…) — mini-spec

Statut : proposition à valider · Auteur : cadrage Rapport · Contexte plateforme : voir le rôle de Rapport (collecte + restitution) dans l'écosystème Heriade (E/T/L).

1. Problème

Aujourd'hui les grandeurs non-financières (capacité de lits, chambres, m², ETP…) sont stockées comme attributs de dimension (DimAttribute / DimAttributeValue) sur l'axe Établissement. Ces attributs sont mono-valeur et atemporels : un établissement = une capacité.

Conséquence : toute reconstitution historique est fausse. Si la capacité passe de 50 (2025) à 100 (2026), relire la valeur courante (100) pour calculer le taux d'occupation 2025 donne TO 2025 = journées / (100 × jours) = 50 % au lieu de 100 %.

Cause racine : la capacité n'est pas un attribut (propriété stable) mais une série temporelle — elle varie par exercice, voire en cours d'année. On l'a rangée dans une structure sans date.

2. Principe retenu

Propriété stable → attribut. Grandeur qui varie dans le temps → mesure, au grain période.

Les données statistiques d'établissement deviennent des éléments de mesure portés sur le même rail que les financières (Movement), au grain établissement × année × mois × scénario. On récupère gratuitement : le versioning, les masques de saisie, le pivot, et la moisson par le workflow.

  • La grandeur (capacité) = une mesure datée.
  • Son unité (lits, chambres, m², ETP) = un attribut statique de l'élément.

3. Modèle de données

3.1 Les éléments statistiques = des « comptes »

Chaque indicateur (capacité totale, capacité permanente, capacité temporaire, chambres en travaux…) est un Account dédié (code statistique), donc une feuille exploitable par tout le moteur. Ils sont rangés dans une dimension dédiée (voir §4).

Champs à ajouter sur l'élément (sur Account ou sur le DimNode selon §4) :

ChampTypeRôle
kind"financier" | "statistique"distingue € des grandeurs physiques
unitstring?unité d'affichage (lits, chambres, , ETP) — attribut statique
timeAgg"flux" | "stock"règle d'agrégation temporelle (cf. §5)

aggregate (somme sur la hiérarchie d'établissements) reste pertinent et déjà implémenté — il sert toujours.

3.2 Les valeurs = des Movement

Aucun nouveau modèle de stockage. Une valeur de capacité = une ligne Movement : scenario × accountCode(statistique) × establishmentCode × year × month × montant.

Le versioning par exercice et par mois est donc intrinsèque, y compris un changement en cours d'année (ouverture d'une aile en juin = valeurs différentes avant/après).

4. Où ça vit : dimension dédiée (recommandé)

Deux options :

  • A — branche dans Catégorie métier/gestion (« Données établissement »). Simple côté UX, mais oblige à blinder « ne jamais remonter dans les totaux € ».
  • B — dimension/axe dédié « Indicateurs établissement » (recommandé). Garantit qu'aucune unité ne se mélange au P&L, et lui laisse ses propres règles d'agrégation.

Reco : option B. Les éléments statistiques forment leur propre axe ; ils ne sont jamais sommés avec des euros.

5. Agrégation — les deux axes n'ont pas la même règle

5.1 Axe temporel : stock vs flux (LE point technique nouveau)

  • flux (CA, journées) → somme des mois = annuel. Comportement actuel.
  • stock (capacité, chambres) → moyenne (pondérée par les jours/coef du mois), ou valeur de fin de période. N'additionne pas janvier + février.

État du code : l'agrégation temporelle par défaut somme (accum dans apps/api/src/services/pivot.ts cumule montant). Bonne nouvelle : la moyenne pondérée existe déjà (mécanisme CumulW + monthCoef, utilisé par la « Vue cumulé »). Le travail = router selon timeAgg : flux → somme, stock → CumulW / coefSum.

5.2 Axe établissement : somme

La capacité d'un parent = somme des feuilles → c'est le flag aggregate déjà en place.

Résumé : somme entre établissements, moyenne dans le temps pour un stock.

6. Scénario

Comme tout est sur le rail Movement, le scénario s'applique : capacité réelle vs budgétée vs N‑1. On peut donc budgéter une hausse de capacité et la comparer au réalisé. À trancher au moment des masques : quel(s) scénario(s) on saisit.

7. Masques de saisie (rôle collecte de Rapport)

Écran de saisie par établissement × exercice, grille 12 mois, un onglet/bloc par élément statistique (ou liste façon « saisie en liste » déjà faite sur les attributs). Sortie : des Movement scenario au choix (réel/budget). C'est le point d'entrée que le workflow moissonne pour produire la source.

8. Lecture dans la fiche

Remplacer la résolution attr actuelle (kind "attr" dans ficheCompute.ts, qui étale une valeur plate sur 12 mois : new Array(12).fill(attrs[name])) par une lecture de la mesure par mois pour le scope établissement courant :

capacite[mois]  ←  Movement(statistique=capacité, établissement courant, exercice, mois)
jours_theo[mois] = capacite[mois] × jours[mois]

→ juste même si la capacité change en juin. La résolution attrByName de apps/api/src/services/forecast.ts est remplacée par une lecture de série mensuelle scope-établissement.

9. Moisson / export vers le workflow

Ce qui est saisi dans Rapport doit être extractible par le workflow, avec clés stables : source, scenario, accountCode(statistique), unit, establishmentCode, year, month, montant, provenance. Le champ provenance (rapport:campagne X / rapport:saisie indicateurs) évite la ré-ingestion en boucle et trace l'origine. Format identique à celui d'un export de campagne figée (budget) — voir le chaînon « export Rapport → workflow » à spécifier par ailleurs.

10. Migration de l'existant

  1. Créer la dimension « Indicateurs établissement » + les Account statistiques (capacité, capacité permanente, capacité temporaire, chambres en travaux…).
  2. Convertir les valeurs DimAttributeValue de capacité actuelles en Movement (scénario réel, étalées sur les mois de l'exercice concerné — valeur stock répétée par mois, pas divisée).
  3. Adapter la fiche EHPAD : ligne capacite = lecture mesure mensuelle (au lieu de l'attr plat).
  4. Conserver DimAttribute pour le vraiment statique (nom, unité, type d'établissement).

11. Impacts code (indicatif)

  • apps/api/prisma/schema.prisma : champs kind / unit / timeAgg sur l'élément ; (pas de nouveau modèle de valeurs — on réutilise Movement).
  • apps/api/src/services/pivot.ts : router l'agrégation temporelle selon timeAgg (réutiliser CumulW/coefOf pour stock).
  • apps/api/src/services/forecast.ts : remplacer attrByName (valeur plate) par lecture de série mensuelle scope-établissement.
  • apps/api/src/services/ficheCompute.ts + apps/web/src/ficheCompute.ts : kind de lecture « mesure mensuelle » (remplace l'attr plat). (NB : compute dupliqué web/api — à factoriser.)
  • Front : masque de saisie des indicateurs (réutiliser l'écran de saisie / la « saisie en liste »).

12. Décisions à trancher

  1. Dimension dédiée (B) ou branche (A) ? → reco B.
  2. timeAgg par défaut pour un nouvel élément statistique : stock ou flux ?
  3. Agrégation stock dans le temps : moyenne pondérée jours ou valeur de fin de période ?
  4. Scénarios saisis dans les masques : réel seul, ou réel + budget ?
  5. Périmètre : on construit local Rapport d'abord (collecte), la bascule « source fait foi à la lecture » vient quand le workflow moissonne ?