Skip to content

Commit cdfb1df

Browse files
authored
feat: simulateur abattoirs (#8)
* chore: documentation métier * wip: evaluate struct & fixtures * feat(abattoirs): moteur DDD + oracle 2744 cas Implémentation des 5 règles métier dans une architecture en bounded contexts pour anticiper l'arrivée du 2e simulateur. * docs: privilégier DSFR puis Tailwind, pas de CSS custom Cadre la stack UI pour rester conforme Beta.gouv et éviter la divergence visuelle. * feat(ui): simulateur abattoirs + versionnage des règles Implémente le formulaire DSFR, le panneau de résultats avec badges accent, et le système de versions lié aux arrêtés officiels avec page d'historique dédiée. * feat(monitoring): ErrorBoundary + handlers globaux + page fallback Capture les erreurs React et JS non gérées via console.error structuré (Phase 1, sans endpoint distant) ; lien historique des versions ouvert dans un nouvel onglet pour préserver le résultat. * chore(vscode): regroupe le réglage importModuleSpecifier déprécié Remplace les deux clés typescript.* et javascript.* par la clé unifiée js/ts.preferences.importModuleSpecifier. * feat: update error page * test(e2e): couvre le simulateur abattoirs et l'historique des versions 19 nouveaux scénarios Playwright couvrant la carte de sélection, le statut conditionnel, la validation, 3 cas connus du moteur et la page d'historique. * refactor: déplace le formatter de date dans shared/utils Utilise Intl.DateTimeFormat à la place du tableau MOIS_FR et sort le formatter du bounded context abattoirs (concept UI générique, pas métier). * feat(a11y): titres de page dynamiques via <title> React 19 Améliore l'a11y (annonce lecteur d'écran) et l'UX onglets en exposant un titre distinct par page, sans dépendance externe. * docs(adr): oracle xlsx, versionnage in-code, monitoring maison Trace les 3 décisions architecturales structurantes prises pendant le développement du simulateur Abattoirs. * feat(abattoirs): icônes thématiques sur le formulaire Repère visuel des 3 sections (réception suidés, abattoir, destinataire) en remplacement des fr-icon-question-line génériques. * chore: sync pnpm-lock.yaml après rebase sur main Resynchronisation du lockfile suite au rebase sur main (jsdom 29 + TS 6) et à l'introduction de xlsx par les commits du moteur abattoirs. * refactor(abattoirs): réordonne ZONE_ORDER du moins au plus restrictif Ordre validé métier : zone indemne → ZI FS → ZP → ZS → ZRI → ZRII → ZRIII (cohérent avec la progression réglementaire et appliqué aux 3 dropdowns du formulaire). * feat(abattoirs): masque le champ statut tant que la zone ne l'exige pas Le champ Statut est sorti du DOM sauf quand zoneSuides est ZRII ou ZRIII (seules zones où le statut a un sens réglementaire), évitant ainsi un champ inerte affiché en gris à l'utilisateur. * fix(abattoirs): réduit la largeur de l'icône cochon Passage de w-6 à w-5 + object-contain pour préserver le ratio naturel, l'icône cochon paraissait visuellement trop large par rapport aux deux autres (building, truck). * feat(abattoirs): masque le panneau de résultats tant qu'aucune simulation n'est lancée Le panneau apparaît uniquement après un clic sur Valider et se masque à nouveau sur Réinitialiser ou modification d'un champ. Supprime la branche placeholder de AbattoirsResult (dead code) et durcit son prop result en non-nullable. * feat(abattoirs): version V2 du 2026-06-05 (TODOs 1, 2, 3, 4 résolus) Le nouveau xlsx tranche les 4 ambiguïtés identifiées : LPS non requis sur les 35 + 336 cas vides, dérogation possible sur les 195 cas UE et les 27 cas Grist vs DOCX, libellé "possible" partout. Règles certification.ts et lps.ts adaptées, oracle régénéré (2 744 cas), entrée versions.ts ajoutée en tête. * docs(abattoirs): renomme V2 en correctif du 2026-06-05 Pas un nouvel arrêté mais une correction des oublis du tableau initial du 2026-05-12, donc « correctif » est plus juste que « V2 » dans la doc, les commentaires des règles et le libellé de l'entrée versions.ts.
1 parent 3791be7 commit cdfb1df

85 files changed

Lines changed: 61506 additions & 309 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
2-
"typescript.preferences.importModuleSpecifier": "relative",
3-
"javascript.preferences.importModuleSpecifier": "relative",
2+
"js/ts.preferences.importModuleSpecifier": "relative",
43
"editor.formatOnSave": true,
54
"editor.defaultFormatter": "esbenp.prettier-vscode",
65
"[typescript]": {

CLAUDE.md

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,65 @@ Cette règle s'applique à :
8989
- Tests : `*.spec.ts` / `*.spec.tsx`
9090
- Hooks personnalisés : `use*.ts`
9191

92+
### 5. Commentaires : simples et brefs
93+
94+
Les commentaires doivent être **courts** et n'expliquer que le **pourquoi** (jamais le quoi évident).
95+
96+
```typescript
97+
// INTERDIT - verbeux, paraphrase le code, multi-lignes inutiles
98+
/**
99+
* Cette fonction prend en entrée les inputs du simulateur Abattoirs
100+
* et applique successivement toutes les règles métier PPA définies
101+
* dans la spécification DOCX du 2026-05-12 pour produire en sortie
102+
* un objet contenant les 7 sorties attendues du moteur.
103+
*/
104+
export function evaluateAbattoir(inputs: AbattoirsInputs): AbattoirsOutputs { ... }
105+
106+
// CORRECT - bref, va à l'essentiel
107+
// Orchestre les 5 règles. Spec : docs/sources/abattoirs-formules-20260512.docx
108+
export function evaluateAbattoir(inputs: AbattoirsInputs): AbattoirsOutputs { ... }
109+
```
110+
111+
Règles :
112+
113+
- Pas de commentaires qui paraphrasent le code (le code est déjà lisible).
114+
- Pas de docblocks JSDoc longs sauf si la fonction a une sémantique non évidente.
115+
- Un commentaire d'une ligne suffit dans 90 % des cas.
116+
- Préférer un nom de variable/fonction explicite à un commentaire d'explication.
117+
- Référencer la source réglementaire (chemin de fichier) en une ligne, pas en paragraphe.
118+
119+
### 6. CSS : DSFR + Tailwind, pas de CSS custom
120+
121+
Pour toute UI, **ordre de priorité strict** :
122+
123+
1. **DSFR** (dernière version, **`@gouvfr/dsfr` 1.14+`** à date) — classes `fr-*`, composants documentés sur [systeme-de-design.gouv.fr](https://www.systeme-de-design.gouv.fr/). C'est le défaut absolu pour layout, formulaires, boutons, badges, alertes, etc.
124+
2. **Tailwind CSS** (v4) — uniquement pour les ajustements utilitaires que le DSFR ne couvre pas (espacements fins, grille spécifique, responsive ponctuel).
125+
3. **CSS custom** (fichier `.css` dédié ou inline) — **uniquement** si DSFR + Tailwind ne suffisent pas, et **après avoir justifié** dans un commentaire pourquoi.
126+
127+
```tsx
128+
// CORRECT — DSFR pour la structure, Tailwind pour le détail
129+
<div className="fr-card fr-card--shadow flex items-center gap-2">
130+
<span className="fr-badge fr-badge--success">Autorisé</span>
131+
</div>
132+
133+
// INTERDIT — CSS inline alors qu'une classe DSFR existe
134+
<div style={{ padding: 16, border: "1px solid #ddd" }}>...</div>
135+
136+
// TOLÉRÉ — uniquement si justifié
137+
<div
138+
className="fr-grid-row"
139+
// Hauteur min imposée par la maquette, non couverte par DSFR
140+
style={{ minHeight: "320px" }}
141+
>
142+
```
143+
144+
Règles :
145+
146+
- Ne jamais réinventer un composant DSFR existant (callout, alert, accordion, badge, etc.).
147+
- En cas de doute, chercher d'abord dans la doc DSFR avant d'écrire du Tailwind.
148+
- Si une override de style DSFR est nécessaire, utiliser `mt-0!` (Tailwind v4 important) plutôt qu'un fichier CSS séparé.
149+
- Pas de framework UI tiers (Material UI, Chakra, etc.) — la conformité Beta.gouv impose DSFR.
150+
92151
## Workflow obligatoire
93152

94153
### Vérification post-implémentation
@@ -124,6 +183,47 @@ Quand une tâche implique un **choix architectural significatif**, créer automa
124183

125184
Ne PAS créer d'ADR pour les corrections de bugs, refactorings mineurs ou fonctionnalités qui suivent un pattern existant.
126185

186+
### Commits : simples et conventionnels
187+
188+
Suivre **Conventional Commits** : un titre court, une seule ligne de description.
189+
190+
Format :
191+
192+
```
193+
<type>(<scope?>): <titre court à l'impératif>
194+
195+
<description en une seule ligne, le pourquoi plus que le quoi>
196+
```
197+
198+
**Types courants** : `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`, `perf`, `build`, `ci`.
199+
200+
**Exemples** :
201+
202+
```
203+
feat(abattoirs): moteur de règles + oracle 2744 cas
204+
205+
Implémentation des 5 règles métier dans une architecture DDD pour préparer l'arrivée du 2e simulateur.
206+
```
207+
208+
```
209+
fix(marque): corrige le cas ZRII MR-PPA dest non MCA
210+
211+
Le LPS retournait null à tort pour la zone destinataire ZRI.
212+
```
213+
214+
```
215+
docs: ADR architecture DDD du moteur
216+
217+
Justifie le découpage en bounded contexts et la séparation data/validation/logique.
218+
```
219+
220+
Règles :
221+
222+
- Titre **sous 70 caractères**, à l'impératif, en minuscules.
223+
- **Une seule ligne** de description (pas de listes à puces, pas de paragraphes).
224+
- Préférer le **pourquoi** au quoi (le diff montre déjà le quoi).
225+
- **Aucune mention d'auteur ou de co-auteur** dans le corps du commit (pas de `Co-Authored-By`, pas de `Generated with`, etc.). L'auteur git suffit.
226+
127227
### Compaction du contexte
128228

129229
Lors de la compaction automatique ou manuelle (`/compact`), TOUJOURS préserver :
@@ -164,37 +264,98 @@ pnpm preview # Prévisualisation du build
164264

165265
## Architecture
166266

167-
Structure organisée par features, avec un moteur de règles isolé en TypeScript pur :
267+
Structure organisée par features (UI) avec un moteur de règles isolé en TypeScript pur, organisé en **DDD propre mais simple** :
168268

169269
```
170270
src/
171-
├── engine/ # Moteur de règles TypeScript pur (sans React)
172-
│ ├── types.ts # Types des inputs/outputs des simulateurs
173-
│ ├── rules.ts # Règles métier PPA
174-
│ └── evaluate.ts # Fonction d'évaluation principale
175-
├── features/ # Une feature = un parcours utilisateur
271+
├── engine/ # Moteur de règles (TypeScript pur, sans React)
272+
│ ├── index.ts # Public API du moteur (re-exports)
273+
│ ├── shared/ # Shared kernel : concepts communs aux contexts
274+
│ │ ├── types.ts # Enums communs (Zone, Statut, Marque, ...)
275+
│ │ └── index.ts
276+
│ ├── abattoirs/ # Bounded context #1
277+
│ │ ├── index.ts # API publique du context
278+
│ │ ├── types.ts # AbattoirsInputs, AbattoirsOutputs
279+
│ │ ├── schema.ts # Schémas Zod (data definition)
280+
│ │ ├── parse.ts # Fonctions de validation (parse/safeParse)
281+
│ │ ├── evaluate.ts # Orchestrateur (evaluateAbattoir)
282+
│ │ ├── evaluate.spec.ts # Test oracle (fixture 2 744 cas)
283+
│ │ └── rules/ # Règles métier atomiques
284+
│ │ └── *.ts + *.spec.ts
285+
│ └── etablissements/ # Bounded context #2 (placeholder)
286+
├── features/ # Une feature = un parcours utilisateur
176287
│ ├── home/pages/
177-
│ └── simulateurs/ # Sous-domaine simulateurs (regroupe les 2 parcours)
288+
│ └── simulateurs/
178289
│ ├── abattoirs/pages/
179290
│ └── etablissements/pages/
180-
└── shared/ # Composants, hooks, utilitaires partagés
181-
├── components/
182-
│ ├── SimulatorForm.tsx
183-
│ ├── ResultPanel.tsx
184-
│ └── layout/Layout.tsx # Header DSFR + Footer DSFR
185-
├── config/routes.config.ts
186-
├── hooks/
187-
└── utils/
291+
└── shared/ # Composants UI, hooks, utilitaires
188292
```
189293

294+
### Principes DDD appliqués
295+
296+
- **Bounded contexts** : un dossier par simulateur sous `src/engine/`. **Aucun import croisé** entre contexts.
297+
- **Shared kernel minimal** : `src/engine/shared/` contient uniquement les concepts vraiment partagés (enums communs aux simulateurs).
298+
- **Public API par context** : un `index.ts` expose les entry points (evaluator, types, parser). Le reste de l'app importe via cet `index.ts` ou via `src/engine/index.ts`.
299+
- **Séparation des couches dans un context** : data (`schema.ts`), validation (`parse.ts`), logique (`rules/` + `evaluate.ts`) sont des fichiers distincts.
300+
- **Pas de couches inutiles** : pas de Repository, pas de Domain Service abstrait, pas de DI container. On reste pragmatique tant que le besoin ne le justifie pas.
301+
190302
**Règle d'or** : le moteur (`src/engine/`) ne doit JAMAIS importer de React ni de dépendances UI. Il doit être testable en pur TypeScript.
191303

192304
## Logique métier
193305

194-
Les règles PPA (Peste Porcine Africaine) sont implémentées dans `src/engine/rules.ts`. Toute modification de ces règles DOIT être accompagnée :
306+
Les règles PPA (Peste Porcine Africaine) sont implémentées dans `src/engine/<context>/rules/`. Toute modification de ces règles DOIT être accompagnée :
307+
308+
1. d'un test Vitest qui couvre le nouveau cas (`*.spec.ts` co-localisé)
309+
2. d'une référence à la source réglementaire (instruction technique, arrêté, ou fichier dans `docs/sources/`) dans un commentaire
310+
311+
## Versionnage des règles métier
312+
313+
Chaque simulateur a son fichier de versions lié aux arrêtés officiels :
314+
315+
- Source de vérité : `src/engine/<context>/versions.ts` (tableau antéchronologique, `[0]` = version courante)
316+
- Affichage : date dans le panneau résultats + page `/historique-versions`
317+
- Procédure PR « nouvel arrêté » détaillée dans [`docs/versions.md`](./docs/versions.md)
318+
319+
Quand un nouvel arrêté entre en vigueur :
320+
321+
1. Copier les sources datées dans `docs/sources/`
322+
2. Régénérer la fixture (`pnpm fixture:abattoirs`)
323+
3. Adapter les règles jusqu'à `pnpm test` vert
324+
4. **Ajouter une entrée en tête de `ABATTOIRS_VERSIONS`** avec `dateEffet` (ISO), `arrete`, `sources`, `changements`, `pullRequest`
325+
5. `pnpm validate`
326+
6. Commit `feat(<context>): nouvelle version YYYY-MM-DD`
327+
328+
La spec `versions.spec.ts` garantit l'ordre antéchronologique strict et le format ISO des dates.
329+
330+
## Monitoring des erreurs
331+
332+
Pas de Sentry — solution maison légère, RGPD-friendly, zéro dépendance.
333+
334+
**Pipeline (Phase 1)** :
335+
336+
1. `installGlobalHandlers()` dans `src/main.tsx` pose `window.error` + `unhandledrejection`.
337+
2. `<ErrorBoundary>` dans `src/App.tsx` wrappe `<Routes>` et capture les erreurs de rendu React.
338+
3. Tous les chemins appellent `reportError(error, context)` qui log un payload structuré via `console.error("[ODICE error]", {})`.
339+
340+
**Visibilité actuelle** : DevTools utilisateur uniquement (console).
341+
342+
**Phase 2 (à venir)** : `reportError` POSTera le payload vers un endpoint configurable via `VITE_ERROR_ENDPOINT` (Mattermost webhook ou petit endpoint Scalingo Node).
343+
344+
**Fichiers** :
345+
346+
- `src/shared/monitoring/error-reporter.ts` — `reportError(error, context)`
347+
- `src/shared/monitoring/install-global-handlers.ts` — listeners globaux
348+
- `src/shared/components/ErrorBoundary.tsx` — boundary React
349+
- `src/features/error/pages/ErrorFallbackPage.tsx` — UI de fallback
350+
351+
**Tester en local** :
352+
353+
```ts
354+
// dans n'importe quel composant
355+
throw new Error("test ErrorBoundary");
356+
```
195357

196-
1. d'un test Vitest qui couvre le nouveau cas (`src/engine/evaluate.spec.ts`)
197-
2. d'une référence à la source réglementaire (instruction technique, arrêté, etc.) dans un commentaire
358+
Ou depuis la console DevTools : `throw new Error("test")` (capté par `window.error`).
198359

199360
## Tests
200361

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# ADR-0002 : Architecture DDD « propre mais simple » pour le moteur ODICE
2+
3+
**Date** : 2026-05-23
4+
**Statut** : Accepté
5+
6+
## Contexte
7+
8+
Le moteur ODICE doit héberger deux simulateurs distincts (Abattoirs, Autres Établissements) qui partagent certains concepts (zones réglementaires, marques sanitaires…) mais ont chacun leur propre logique métier, leurs propres inputs et leurs propres outputs.
9+
10+
L'arborescence initiale (`src/engine/{types,rules,evaluate}.ts` à plat) ne tient pas la charge dès qu'on commence à implémenter les 5 fonctions de règles + leurs schémas Zod + leurs tests. On observe :
11+
12+
- un fichier `types.ts` qui mélange enums partagés et types spécifiques à un simulateur,
13+
- un fichier `schemas.ts` qui mélange définitions de données (schémas Zod) et logique de validation (fonctions parse),
14+
- aucun frontière nette entre les deux simulateurs (risque d'imports croisés involontaires).
15+
16+
## Décision
17+
18+
> Nous structurons `src/engine/` en **bounded contexts DDD**, avec un shared kernel minimal et une séparation explicite des responsabilités dans chaque context, sans introduire de patterns plus lourds (Repository, DI, Domain Service abstrait…).
19+
20+
Structure :
21+
22+
```
23+
src/engine/
24+
├── index.ts # Public API du moteur (re-exports)
25+
├── shared/ # Shared kernel
26+
│ ├── types.ts # Enums communs (Zone, Statut, Marque, ...)
27+
│ └── index.ts
28+
├── abattoirs/ # Bounded context #1
29+
│ ├── index.ts # API publique du context
30+
│ ├── types.ts # AbattoirsInputs, AbattoirsOutputs
31+
│ ├── schema.ts # Schémas Zod (data definition pure)
32+
│ ├── parse.ts # Fonctions de validation
33+
│ ├── evaluate.ts # Orchestrateur
34+
│ ├── evaluate.spec.ts # Test oracle (fixture 2 744 cas)
35+
│ └── rules/ # Règles atomiques
36+
│ └── *.ts + *.spec.ts
37+
└── etablissements/ # Bounded context #2 (placeholder)
38+
```
39+
40+
## Options envisagées
41+
42+
### Option A — DDD « propre mais simple » (retenue)
43+
44+
- Avantages :
45+
- Isolation forte entre simulateurs (aucun import croisé possible par convention).
46+
- Public API explicite via `index.ts` par context.
47+
- Séparation des couches data / validation / logique à l'intérieur d'un context.
48+
- Évolutif : ajouter un nouveau simulateur = créer un nouveau dossier sans toucher l'existant.
49+
- Inconvénients :
50+
- Plus de fichiers que le « flat » initial.
51+
- Un peu plus de boilerplate (index.ts par dossier).
52+
53+
### Option B — Flat (`src/engine/{types,rules,evaluate}.ts`)
54+
55+
- Avantages :
56+
- Très peu de fichiers.
57+
- Inconvénients :
58+
- Ne tient pas la charge à plus d'un simulateur.
59+
- Pas de frontière naturelle entre simulateurs.
60+
- Mélange data definition / validation / logique dans un même fichier.
61+
62+
### Option C — DDD « tactical » complet (Aggregates, Domain Services, Value Objects, Repository, DI…)
63+
64+
- Avantages :
65+
- Architecture la plus pure.
66+
- Inconvénients :
67+
- Sur-ingénierie pour un moteur de règles déterministe sans persistance.
68+
- Cohérence avec le style « pragmatique Beta.gouv » discutable.
69+
- Coût d'entrée pour les contributeurs.
70+
71+
## Conséquences
72+
73+
### Positives
74+
75+
- Le moteur reste pur TypeScript, testable sans React (règle d'or maintenue).
76+
- Chaque simulateur peut évoluer indépendamment.
77+
- Les imports sont prévisibles : `@engine` (racine), `@engine/abattoirs`, `@engine/shared`.
78+
- L'arrivée du 2e simulateur (Établissements) ne nécessitera aucun refactor — il suffira de remplir le dossier `etablissements/`.
79+
80+
### Négatives / Risques
81+
82+
- Surface plus large à parcourir au démarrage. **Mitigation** : section Architecture du `CLAUDE.md` explicite.
83+
- Tentation d'ajouter des couches au fil du temps. **Mitigation** : règle « pas de couches inutiles » dans CLAUDE.md.
84+
85+
### Migration
86+
87+
Effectuée dans cette même PR :
88+
89+
1. Création de `shared/`, `abattoirs/`, `etablissements/` avec leurs `index.ts`.
90+
2. Éclatement de `types.ts``shared/types.ts` (enums communs) + `<context>/types.ts` (types spécifiques).
91+
3. Split de `schemas.ts``abattoirs/schema.ts` (Zod pur) + `abattoirs/parse.ts` (fonctions).
92+
4. Suppression des anciens fichiers racine (`types.ts`, `schemas.ts`, `evaluate.ts`, `evaluate.spec.ts`, `rules.ts`).
93+
5. Création de `src/engine/index.ts` qui re-export tout.
94+
6. Ajout de l'alias `@engine` (sans wildcard) dans tsconfig (Vite/Vitest l'avaient déjà).
95+
7. Adaptation de `src/shared/components/ResultPanel.tsx` (1 ligne d'import).
96+
8. Migration du script de fixture `.mjs``.ts` avec imports typés depuis les enums du moteur (typecheck garantit la cohérence du mapping xlsx → enum).
97+
98+
`pnpm validate` reste vert après chacune des étapes.
99+
100+
## Liens
101+
102+
- Documentation métier : [`docs/simulateur-abattoirs.md`](../simulateur-abattoirs.md)
103+
- Points à valider avec l'équipe métier : [`docs/simulateur-abattoirs-points-a-valider.md`](../simulateur-abattoirs-points-a-valider.md)
104+
- CLAUDE.md, section « Architecture »

0 commit comments

Comments
 (0)