Wiki/docs/05-data-dictionary.md
Corentin JOGUET 668576cdc4 chore: initial commit — formation-hub conception phase
Conception complete (Phase 0) pour formation-hub Acadenice :

- 19 docs Merise Agile + UML + GitOps + plans (tests/deploy/ops/api)
  cf docs/00-readme.md pour l'index complet
- Stack Docker compose (Docmost + Baserow + Postgres + Redis + MinIO local FS)
  compose.yml + compose.staging.yml + compose.prod.yml
- CI/CD GitHub Actions skeleton (ci, deploy-staging, deploy-prod)
- Bridge service skeleton (Hono + TS + Biome + Vitest + zod + pino)
- Templates GitHub : PR + 3 issue types + CODEOWNERS + dependabot.yml
- Scripts ops : healthcheck, backup quotidien, smoke-test post-deploy
- LICENSE AGPL-3.0 + SECURITY.md + CONTRIBUTING.md + CHANGELOG.md
- Diagramme drawIO archi infra (XML importable dans diagrams.net)

Decisions structurelles enregistrees :
- Scope CFA + Agence avec entite PERSONNE pivot multi-roles (ADR-001)
- Stack composite Docmost AGPL + Baserow MIT + bridge custom (ADR-001)
- Path B : UX quasi-unified via Tiptap node-views custom (ADR-002)
- Monorepo trunk-based development (ADR-003)
- Postgres separe Docmost/Baserow (ADR-004)
- Bridge stack Node 22 + Hono (ADR-005)
- Repo neuf prefere a fork Docmost
- Prod-like des le jour 1 (pas MVP)
2026-05-07 12:16:19 +02:00

220 lines
12 KiB
Markdown

# Data Dictionary
> Dictionnaire de donnees complet du domaine **CFA + Agence d'Acadenice**.
> Source de verite pour le MCD, MLD et MPD. Mantra BYAN #33 : Data Dictionary First.
> Scope B valide : entite PERSONNE pivot multi-roles (formateur, developpeur, admin).
> **Etudiants** : non modelises ici, juste users Docmost.
## Conventions
- Codes en `snake_case`, prefixes par mnemonique d'entite
- **Source** : `S` saisi, `C` calcule (rollup/formula), `A` automatique (timestamp/sequence)
- **Type abstrait** : independant de la techno (mapping Postgres + Baserow plus loin)
- **Nullable** : `O` oui, `N` non
## Mapping types abstrait → Postgres → Baserow
| Type abstrait | Postgres | Baserow |
|---------------|----------|---------|
| `INT` | `INTEGER` ou `BIGINT` (PK) | `Number` (sans decimal) |
| `DECIMAL(p,s)` | `NUMERIC(p,s)` | `Number` (avec decimal) |
| `VARCHAR(n)` | `VARCHAR(n)` | `Text` |
| `TEXT` | `TEXT` | `Long text` |
| `DATE` | `DATE` | `Date` |
| `TIMESTAMPTZ` | `TIMESTAMP WITH TIME ZONE` | `Last modified time` / `Created time` |
| `ENUM(...)` | `VARCHAR + CHECK` ou type ENUM | `Single select` |
| `MULTI_ENUM(...)` | tableau VARCHAR ou table associative | `Multiple select` |
| `EMAIL` | `VARCHAR(254) + CHECK regex` | `Email` |
| `FK` | `INTEGER + REFERENCES` | `Link to table` |
---
# Section 1 — Entite pivot
## Entite PERSONNE
Centrale au modele. Une personne peut cumuler plusieurs roles. Sa capacite annuelle totale se split entre formation et agence.
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `personne_id` | Identifiant | INT | N | seq | A | PK |
| `personne_nom` | Nom de famille | VARCHAR(100) | N | — | S | trim |
| `personne_prenom` | Prenom | VARCHAR(100) | N | — | S | trim |
| `personne_email` | Email pro | EMAIL | N | — | S | UNIQUE, format email |
| `personne_telephone` | Telephone | VARCHAR(20) | O | NULL | S | format E.164 si rempli |
| `personne_capacite_annuelle` | Capacite totale heures/an | DECIMAL(6,2) | N | 0 | S | `>= 0` |
| `personne_split_formation_pct` | Part allouee formation | DECIMAL(4,1) | N | 50.0 | S | `0-100`, `+ split_agence_pct = 100` |
| `personne_split_agence_pct` | Part allouee agence | DECIMAL(4,1) | N | 50.0 | S | `0-100` |
| `personne_roles` | Roles cumules | MULTI_ENUM | N | — | S | `formateur \| developpeur \| admin \| direction \| support` |
| `personne_heures_attribuees_formation` | Cumul heures formation attribuees | DECIMAL(6,2) | N | 0 | C | rollup `SUM(ATTRIBUTION.heures)` |
| `personne_heures_attribuees_agence` | Cumul heures agence attribuees | DECIMAL(6,2) | N | 0 | C | rollup `SUM(INTERVENTION.heures)` |
| `personne_heures_restantes_formation` | Capacite formation restante | DECIMAL(6,2) | N | 0 | C | formula |
| `personne_heures_restantes_agence` | Capacite agence restante | DECIMAL(6,2) | N | 0 | C | formula |
| `personne_heures_restantes_total` | Capacite totale restante | DECIMAL(6,2) | N | 0 | C | formula |
| `personne_statut` | Statut | ENUM | N | `actif` | S | `actif \| inactif` |
**Formules cles** :
```
personne_heures_restantes_formation = (capacite_annuelle * split_formation_pct / 100) - heures_attribuees_formation
personne_heures_restantes_agence = (capacite_annuelle * split_agence_pct / 100) - heures_attribuees_agence
personne_heures_restantes_total = capacite_annuelle - heures_attribuees_formation - heures_attribuees_agence
```
**Note** : un admin pur (pas formateur ni developpeur) peut avoir `capacite_annuelle = 0` et splits a 0.
---
# Section 2 — Branche CFA
## Entite FORMATION
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `formation_id` | Identifiant | INT | N | seq | A | PK |
| `formation_nom` | Nom | VARCHAR(200) | N | — | S | UNIQUE, trim |
| `formation_description` | Description | TEXT | O | NULL | S | — |
| `formation_filiere` | Filiere | ENUM | O | NULL | S | `dev \| graphisme \| marketing \| iot \| cybersec` |
| `formation_heures_totales` | Heures totales | DECIMAL(6,2) | N | 0 | S | `>= 0` |
| `formation_heures_attribuees` | Heures attribuees blocs | DECIMAL(6,2) | N | 0 | C | rollup |
| `formation_heures_restantes` | Restantes | DECIMAL(6,2) | N | 0 | C | formula |
| `formation_statut` | Statut | ENUM | N | `draft` | S | `draft \| actif \| termine \| archive` |
| `formation_date_debut` | Date debut | DATE | O | NULL | S | — |
| `formation_date_fin` | Date fin | DATE | O | NULL | S | `>= date_debut` |
| `formation_created_at` | Cree le | TIMESTAMPTZ | N | NOW() | A | — |
| `formation_updated_at` | Modifie le | TIMESTAMPTZ | N | NOW() | A | — |
## Entite BLOC
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `bloc_id` | Identifiant | INT | N | seq | A | PK |
| `bloc_formation_id` | Formation parente | INT FK | N | — | S | FK → FORMATION, CASCADE |
| `bloc_nom` | Nom | VARCHAR(200) | N | — | S | UNIQUE par formation |
| `bloc_description` | Description | TEXT | O | NULL | S | — |
| `bloc_heures_prevues` | Heures du bloc | DECIMAL(6,2) | N | 0 | S | `>= 0` |
| `bloc_heures_attribuees` | Heures modules | DECIMAL(6,2) | N | 0 | C | rollup |
| `bloc_heures_restantes` | Restantes | DECIMAL(6,2) | N | 0 | C | formula |
| `bloc_ordre` | Ordre dans formation | INT | N | 0 | S | `>= 0` |
## Entite MODULE
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `module_id` | Identifiant | INT | N | seq | A | PK |
| `module_bloc_id` | Bloc parent | INT FK | N | — | S | FK → BLOC, CASCADE |
| `module_nom` | Nom | VARCHAR(200) | N | — | S | trim |
| `module_description` | Description | TEXT | O | NULL | S | — |
| `module_heures_prevues` | Heures prevues | DECIMAL(5,2) | N | 0 | S | `>= 0` |
| `module_heures_attribuees` | Heures attribuees | DECIMAL(5,2) | N | 0 | C | rollup |
| `module_heures_realisees` | Heures realisees | DECIMAL(5,2) | N | 0 | C | rollup |
| `module_statut` | Cycle de vie | ENUM | N | `a_attribuer` | S | `a_attribuer \| attribue \| en_cours \| realise \| annule` |
## Entite ATTRIBUTION (Module ↔ Personne[formateur])
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `attribution_id` | Identifiant | INT | N | seq | A | PK |
| `attribution_module_id` | Module attribue | INT FK | N | — | S | FK → MODULE, CASCADE |
| `attribution_personne_id` | Formateur (Personne) | INT FK | N | — | S | FK → PERSONNE, RESTRICT. Personne doit avoir role `formateur` |
| `attribution_heures_attribuees` | Heures planifiees | DECIMAL(5,2) | N | 0 | S | `> 0` |
| `attribution_heures_realisees` | Heures effectuees | DECIMAL(5,2) | N | 0 | S | `>= 0` |
| `attribution_date_debut` | Debut periode | DATE | O | NULL | S | — |
| `attribution_date_fin` | Fin periode | DATE | O | NULL | S | `>= date_debut` |
| `attribution_statut` | Statut | ENUM | N | `planifie` | S | `planifie \| en_cours \| realise \| annule` |
---
# Section 3 — Branche AGENCE
## Entite CLIENT
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `client_id` | Identifiant | INT | N | seq | A | PK |
| `client_nom` | Nom client | VARCHAR(200) | N | — | S | UNIQUE |
| `client_contact_principal` | Contact (Nom + role) | VARCHAR(200) | O | NULL | S | — |
| `client_contact_email` | Email contact | EMAIL | O | NULL | S | format email |
| `client_contact_telephone` | Telephone | VARCHAR(20) | O | NULL | S | — |
| `client_secteur` | Secteur d'activite | VARCHAR(100) | O | NULL | S | — |
| `client_notes` | Notes libres | TEXT | O | NULL | S | — |
| `client_statut` | Statut | ENUM | N | `prospect` | S | `prospect \| actif \| inactif \| archive` |
| `client_created_at` | Cree le | TIMESTAMPTZ | N | NOW() | A | — |
## Entite PROJET
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `projet_id` | Identifiant | INT | N | seq | A | PK |
| `projet_client_id` | Client | INT FK | N | — | S | FK → CLIENT, RESTRICT |
| `projet_nom` | Nom projet | VARCHAR(200) | N | — | S | UNIQUE par client |
| `projet_description` | Description | TEXT | O | NULL | S | — |
| `projet_type` | Type | ENUM | O | NULL | S | `site_web \| app_mobile \| api \| infra \| audit \| support \| autre` |
| `projet_charge_heures` | Charge estimee | DECIMAL(7,2) | N | 0 | S | `>= 0` |
| `projet_heures_attribuees` | Heures attribuees taches | DECIMAL(7,2) | N | 0 | C | rollup |
| `projet_heures_realisees` | Heures realisees | DECIMAL(7,2) | N | 0 | C | rollup |
| `projet_heures_restantes` | Restantes | DECIMAL(7,2) | N | 0 | C | formula |
| `projet_date_debut` | Date debut | DATE | O | NULL | S | — |
| `projet_date_fin_prevue` | Date fin prevue | DATE | O | NULL | S | `>= date_debut` |
| `projet_date_livraison` | Date livraison effective | DATE | O | NULL | S | — |
| `projet_statut` | Statut | ENUM | N | `devis` | S | `devis \| en_cours \| livre \| cloture \| abandonne` |
| `projet_formation_id` | Formation pedagogique liee | INT FK | O | NULL | S | FK → FORMATION (lien optionnel pour projets pedagogiques) |
| `projet_url` | URL livraison | VARCHAR(500) | O | NULL | S | format URL |
| `projet_repository` | URL repo Git | VARCHAR(500) | O | NULL | S | format URL |
## Entite TACHE
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `tache_id` | Identifiant | INT | N | seq | A | PK |
| `tache_projet_id` | Projet parent | INT FK | N | — | S | FK → PROJET, CASCADE |
| `tache_titre` | Titre | VARCHAR(200) | N | — | S | trim |
| `tache_description` | Description | TEXT | O | NULL | S | — |
| `tache_charge_heures` | Charge estimee | DECIMAL(5,2) | N | 0 | S | `>= 0` |
| `tache_heures_realisees` | Heures realisees | DECIMAL(5,2) | N | 0 | C | rollup |
| `tache_priorite` | Priorite | ENUM | O | NULL | S | `faible \| normale \| haute \| critique` |
| `tache_statut` | Statut | ENUM | N | `todo` | S | `todo \| in_progress \| review \| done \| abandoned` |
| `tache_date_debut` | Debut prevu | DATE | O | NULL | S | — |
| `tache_date_fin_prevue` | Fin prevue | DATE | O | NULL | S | `>= date_debut` |
## Entite INTERVENTION (Tache ↔ Personne[developpeur])
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|------|-------------|------|----------|---------|--------|-------------|
| `intervention_id` | Identifiant | INT | N | seq | A | PK |
| `intervention_tache_id` | Tache | INT FK | N | — | S | FK → TACHE, CASCADE |
| `intervention_personne_id` | Developpeur (Personne) | INT FK | N | — | S | FK → PERSONNE, RESTRICT. Personne doit avoir role `developpeur` |
| `intervention_heures` | Heures effectuees | DECIMAL(5,2) | N | 0 | S | `> 0` |
| `intervention_date` | Date intervention | DATE | N | TODAY | S | — |
| `intervention_notes` | Notes / commit ref | TEXT | O | NULL | S | — |
| `intervention_statut` | Statut | ENUM | N | `realise` | S | `planifie \| realise \| annule` |
---
# Section 4 — Cardinalites synthetiques
| Relation | Source | Cible | Cardinalite |
|----------|--------|-------|-------------|
| FORMATION → BLOC | (1,N) | (1,1) | une formation a au moins 1 bloc |
| BLOC → MODULE | (1,N) | (1,1) | un bloc a au moins 1 module |
| MODULE ↔ PERSONNE via ATTRIBUTION | (0,N) | (0,N) | n-n porteuse |
| CLIENT → PROJET | (0,N) | (1,1) | un client peut avoir 0+ projets |
| PROJET → TACHE | (0,N) | (1,1) | un projet peut etre vide ou avoir des taches |
| TACHE ↔ PERSONNE via INTERVENTION | (0,N) | (0,N) | n-n porteuse |
| PROJET ↔ FORMATION | (0,1) | (0,N) | lien optionnel projet pedagogique |
# Section 5 — Volumetrie estimee (an 1 / an 5)
| Entite | An 1 | An 5 |
|--------|------|------|
| PERSONNE | ~30 | ~80 |
| FORMATION | ~10 | ~50 |
| BLOC | ~50 | ~250 |
| MODULE | ~500 | ~2500 |
| ATTRIBUTION | ~600 | ~3000 |
| CLIENT | ~10 | ~50 |
| PROJET | ~20 | ~150 |
| TACHE | ~200 | ~2000 |
| INTERVENTION | ~2000 | ~20000 |
Volumetrie negligeable cote performance — indexation standard sur les FK suffit.