# Édition PDF « Conformité formule »

## Fonctionnalité
Nouvelle entrée **Imprimé > Conformité formule** sur la fiche formule. Elle ouvre dans un nouvel onglet un PDF (html2pdf) qui confronte la composition de la formule (centrale + variante sélectionnées dans l'onglet Composition) aux exigences de `t_norme_contrainte` (tableau NA.F.1 du PR NF EN 206).

Contenu : en-tête (centrale, logo `t_parametre.photo_logo`), identification (code, libellé, classes, Dmax, plasticité, chlorure), tableau de composition (type, libellé, fournisseur, quantité), puis les blocs de conformité : chlorures, alcalins, Eeff/(C+kA), résistances cylindre/cube, dosage minimal en liant, air, rapport A/(A+C), type de ciment. Le bandeau « Formulation conforme / non conforme » (vert/rouge) résume tous les contrôles.

## Fichiers
- `app/Services/formule_conformite_service.php` (nouveau) : calculs + génération Html2Pdf.
- `resources/views/formule/pdf_conformite.blade.php` (nouveau) : gabarit du PDF.
- `app/Http/Controllers/formule_controller.php` : méthode `imprimer_conformite_pdf()`.
- `routes/web.php` : `GET /formule/imprimer_conformite_pdf` (`formule.imprimer_conformite_pdf`, middleware `auth:sanctum` + `interne`).
- `resources/views/formule/form.blade.php` : entrée de menu + handler JS.

## Sources des données
| Donnée | Source |
|---|---|
| Classe exposition / norme | `t_classe_exposition.classe_exposition_norme_code` uniquement (pas de repli sur le libellé) ; si vide/`Aucun` ou absent de `t_norme_contrainte`, la formule est non conforme ; classe composée (`XC4/XF1`) → exigence la plus sévère de chaque classe |
| Dmax, air | `t_formule_detail.formule_granularite`, `formule_air` |
| Chlorure max | nombre extrait du libellé de `t_classe_chlorure` (`Cl 0.65`) |
| Résistance de la formule | `t_classe_resistance.classe_resistance_norme_code` (ex. `C30/37` → 30 cylindre / 37 cube) ; si le code n'est pas exploitable (`Aucun`), la résistance est signalée non renseignée et la formule non conforme |
| Résistances mini | `t_norme_contrainte.classe_resistance_na` |
| Liant mini, Eeff/liant max, air mini (%), type ciment | `liant_na`, `ecka_na`, `air_mini`, `nature_ciment_na` |
| Rapport max A/(A+C) | colonne `cem*_*_na` selon type ciment / type addition (même correspondance que `composition_calcul_trait`) |
| Spécificité | `t_specificite.specificite_libelle` (libellé « Spécificité » dans l'en-tête, à la place de « Catégorie de chaussée en béton » de la maquette) |

## Décisions / hypothèses
- Calculs (ciment, addition retenue, C+kA, alcalins, chlorures) recopiés de `composition_calcul_trait` plutôt qu'appelés, car le trait plante si la classe d'exposition n'a pas de norme et divise par zéro sans liant. **Différence volontaire** : si le type d'addition n'est pas reconnu, le trait prend `qteC` comme quantité d'addition (bug probable) ; le service prend la quantité réelle.
- Modulation du dosage minimal en liant équivalent (défini pour Dmax = 20 mm) suivant le Dmax (`modulationDmax()`), règle fournie par le métier : ≤ 12,5 mm +10 % ; 14 mm +7,5 % ; 16 mm +5 % ; 20 mm 0 ; 22,4 mm −2,5 % ; ≥ 31,5 mm −10 %. Entre deux paliers : interpolation linéaire (ex. 18 mm → +2,5 %) — **à confirmer**.
- Eau des adjuvants : `masse × (1 − extrait sec)` (0 si extrait sec non renseigné) ; seuil affiché 3 l/m3.
- Agent entraîneur d'air détecté par le libellé/type de l'adjuvant (« entraîneur » ou mot « air »).
- Chlorures en % de la masse de ciment.
