Appearance
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) :
| Champ | Type | Rôle |
|---|---|---|
kind | "financier" | "statistique" | distingue € des grandeurs physiques |
unit | string? | unité d'affichage (lits, chambres, m², 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
- Créer la dimension « Indicateurs établissement » + les
Accountstatistiques (capacité, capacité permanente, capacité temporaire, chambres en travaux…). - Convertir les valeurs
DimAttributeValuede capacité actuelles enMovement(scénario réel, étalées sur les mois de l'exercice concerné — valeur stock répétée par mois, pas divisée). - Adapter la fiche EHPAD : ligne
capacite= lecture mesure mensuelle (au lieu de l'attrplat). - Conserver
DimAttributepour le vraiment statique (nom, unité, type d'établissement).
11. Impacts code (indicatif)
apps/api/prisma/schema.prisma: champskind/unit/timeAggsur l'élément ; (pas de nouveau modèle de valeurs — on réutiliseMovement).apps/api/src/services/pivot.ts: router l'agrégation temporelle selontimeAgg(réutiliserCumulW/coefOfpourstock).apps/api/src/services/forecast.ts: remplacerattrByName(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'attrplat). (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
- Dimension dédiée (B) ou branche (A) ? → reco B.
timeAggpar défaut pour un nouvel élément statistique :stockouflux?- Agrégation
stockdans le temps : moyenne pondérée jours ou valeur de fin de période ? - Scénarios saisis dans les masques : réel seul, ou réel + budget ?
- Périmètre : on construit local Rapport d'abord (collecte), la bascule « source fait foi à la lecture » vient quand le workflow moissonne ?
