Ratios et alertes en Python
Solvéa Énergies affiche un ROE de 18,3 %, le meilleur des trois. C'est aussi la société qui a brûlé 47 millions d'euros de trésorerie en 2025 et dont le délai de paiement clients est passé de 101 à 143 jours. Un ratio isolé ment ; c'est la combinaison qui informe, et elle n'a de valeur que si chaque ratio est calculé de la même façon pour les trois sociétés, avec une définition écrite. Cette leçon calcule 16 ratios en Python, les formate une seule fois, et en tire des alertes qui citent leur page.
Le mécanisme : une définition, un calcul, un format
flowchart LR E["États normalisés en k€"] --> R["ratios_exercice : 16 ratios + EBITDA"] D["DEFINITIONS : libellé et formule"] --> T["Tableau de bord"] R --> F["formater : un seul format"] F --> T F --> M["Faits du mémo"] R --> A["Règles d'alerte + pages des états"] A --> T A --> M
Les définitions vivent dans le code, à côté du calcul, et sont affichées telles quelles sous chaque ratio du tableau de bord : un analyste qui conteste une valeur voit la formule utilisée.
DEFINITIONS: dict[str, tuple[str, str]] = {
"roe": ("ROE", "résultat net / capitaux propres moyens (N et N-1)"),
"levier": ("Levier", "dette nette / EBITDA"),
"bfr_jours": ("BFR en jours de CA", "BFR / CA x 365"),
"free_cash_flow": ("Free cash-flow (k€)", "flux de trésorerie d'exploitation - investissements"),
...
}Quelques définitions demandent une décision explicite :
| Ratio | Choix retenu | Alternative | Pourquoi |
|---|---|---|---|
| ROE | capitaux propres moyens N et N-1 | capitaux propres de clôture | Solvéa a augmenté son capital de 15 M€ en 2025 : la clôture sous-estime la rentabilité de l'année |
| EBITDA | résultat d'exploitation + dotations | EBITDA « ajusté » de la société | l'ajusté de Ferlac (25 140 k€) exclut des charges que l'entreprise a bien payées |
| Délai clients | créances / CA x 365 | créances / CA TTC x 365 | le CA TTC n'est pas publié ; le ratio sert à comparer, la convention est affichée |
| Conversion en cash | FCF / résultat net | FCF / EBITDA | non significative si le résultat est une perte (affichée « n.s. ») |
# EBITDA négatif : le quotient serait négatif et passerait sous tous les seuils.
"levier": _div(dette_nette, ebitda) if ebitda > 0 else None,
# Une conversion rapportée à une perte n'a pas de sens : non significatif.
"conversion_fcf": _div(fcf, cr["resultat_net"]) if cr["resultat_net"] > 0 else None,Le format est unique (formater) : le tableau de bord et le mémo affichent la même chaîne, « 5,5x », « 19,8 % », « -14 930 k€ ». C'est ce qui permettra, à la leçon suivante, de vérifier mot pour mot que le mémo n'invente aucun nombre.
Une société a une dette nette de 40 M€ et un EBITDA de -5 M€. Le levier calculé vaut -8x et l'alerte « levier > 3x » ne se déclenche pas. Où corrigez-vous ?
« levier < 0 » déclencherait aussi l'alerte pour une société en trésorerie nette (Helvane : -0,6x), ce qui est l'inverse d'un risque. Le défaut est dans la définition : on corrige le ratio et on ajoute une règle explicite. Confier la détection au rédacteur, c'est revenir à un calcul par le modèle.
Les alertes : des règles lisibles, des seuils discutables
SEUILS = {
"levier_max": 3.0, # dette nette / EBITDA
"couverture_min": 3.0, # résultat d'exploitation / charges financières
"hausse_dso_jours": 15, # dérive du délai clients en un an
"baisse_marge_points": 1.0 # points de marge d'EBITDA perdus en un an
}
...
if cr["resultat_net"] > 0 and r["free_cash_flow"] < 0:
ajouter("moyenne", "Bénéfice non converti en trésorerie",
f"Résultat net de {formater('montant', cr['resultat_net'])} mais free cash-flow de "
f"{formater('free_cash_flow', r['free_cash_flow'])} en {n}.", [p_cr, p_fl])Chaque alerte porte un identifiant stable (A-solvea-2), une gravité, un constat chiffré et les pages des états dont elle provient. Les seuils sont des choix d'analyste : ils sont en tête de fichier, commentés, pour être discutés en comité plutôt que découverts dans un prompt.
Résultat obtenu (2026-10-15)
Extrait de exemple/analyse.json, produit par make e2e :
| Ratio 2025 | Maison Ferlac SAS | Helvane Robotique SA | Solvéa Énergies SA |
|---|---|---|---|
| Croissance du CA | 2,5 % | 12,0 % | 45,4 % |
| Marge d'EBITDA | 5,7 % | 19,8 % | 13,6 % |
| ROE | -2,1 % | 17,4 % | 18,3 % |
| Levier | 5,5x | -0,6x | 2,3x |
| Couverture des intérêts | 0,7x | 33,8x | 4,0x |
| Délai clients (jours) | 76 j | 75 j | 143 j |
| Free cash-flow (k€) | -14 930 k€ | 13 400 k€ | -47 225 k€ |
| Conversion en cash | n.s. | 0,7x | -4,2x |
A-ferlac-1 haute Endettement élevé | Dette nette / EBITDA de 5,5x en 2025 (3,6x en 2024). [4, 5]
A-ferlac-2 haute Intérêts mal couverts | Le résultat d'exploitation couvre 0,7x les charges financières en 2025. [4]
A-ferlac-5 moyenne Dividende supérieur au résultat | Dividendes versés de 4 500 k€ pour un résultat net de -1 410 k€ en 2025. [4, 6]
A-solvea-2 moyenne Bénéfice non converti en trésorerie | Résultat net de 11 175 k€ mais free cash-flow de -47 225 k€ en 2025. [4, 6]
A-solvea-3 haute Dérive des créances clients | Délai clients passé de 101 j à 143 j. [4, 5]Helvane ne déclenche aucune règle. Les écarts de ratios avec la vérité terrain sont nuls : les ratios calculés sur l'extraction et sur les annotations sont identiques.
Ce qui casse en production
- Le signe qui inverse le sens. Levier négatif (trésorerie nette ou EBITDA négatif), conversion en cash sur une perte : un quotient calculé sans garde-fou produit une valeur qui passe sous les seuils. Testez chaque ratio aux bornes (
test_ebitda_negatif_ne_masque_pas_la_dette). - Les seuils universels. 3x de levier est prudent pour un industriel, normal pour une foncière. Symptôme : des alertes systématiques sur un secteur, que les analystes apprennent à ignorer. Parade : des seuils par secteur, dans un fichier versionné.
- Les ratios sur des exercices non comparables. Solvéa a changé de méthode de reconnaissance du chiffre d'affaires en 2025 : sa croissance de 45,4 % inclut 14 800 k€ dus au seul changement de méthode. Le ratio est juste, sa lecture non. Seul le signal qualitatif (leçon 5) le révèle.
- Le format dupliqué. Si le JavaScript reformate les ratios de son côté, « 5,5x » et « 5,46x » coexistent et la vérification des nombres du mémo échoue. Un seul
formater, côté Python.
Règle de décision
Tout ratio qui sort du système a une formule écrite, un format unique et un test aux bornes. Une alerte est une règle de deux lignes, pas un jugement du modèle. Revoyez les seuils une fois par an avec les analystes, et mesurez combien d'alertes ont été jugées utiles : moins de la moitié, et les seuils sont à revoir.
Pratique
Le comité trouve que l'alerte « Free cash-flow négatif » se déclenche trop souvent sur des sociétés en forte croissance qui investissent. Que proposez-vous ?
Le code fait déjà la seconde moitié : gravité haute seulement si les deux exercices sont négatifs. Supprimer l'alerte retirerait l'information principale sur Solvéa. Laisser le modèle relativiser, c'est déplacer une règle dans un prompt où personne ne la relit.
Un analyste conteste le ROE de 18,3 % de Solvéa : il obtient 15,1 % avec les capitaux propres de clôture. Qui a raison ?
Il n'existe pas de ROE unique. L'intérêt d'avoir la formule affichée est précisément de déplacer le débat vers la convention. Avec 15 M€ d'augmentation de capital dans l'année, la moyenne est plus juste ; l'analyste peut préférer la clôture pour comparer à un historique, et c'est un choix légitime.
Exercice. Ajoutez le ratio « intensité d'investissement » (investissements / dotations aux amortissements) et une alerte « Sous-investissement » quand il est inférieur à 0,8 deux années de suite. Quelle société déclenche l'alerte ?
Voir la solution
"intensite_invest": ("Intensité d'investissement", "investissements / dotations aux amortissements"),
# dans ratios_exercice :
"intensite_invest": _div(fl["investissements"], cr["dotations_amortissements"]),
# dans formater, avec les ratios en « x » :
if cle in {"levier", "couverture_interets", "conversion_fcf", "intensite_invest"}: if all(x["intensite_invest"] is not None and x["intensite_invest"] < 0.8 for x in (r, r1)):
ajouter("moyenne", "Sous-investissement",
f"Investissements inférieurs à 80 % des dotations en {n1} et {n}.", [p_fl, p_cr])Aucune des trois sociétés ne la déclenche : Helvane investit 1,3x et 1,4x ses dotations, Ferlac 1,1x et 0,9x, Solvéa 2,4x et 3,1x. C'est un résultat utile : une règle qui ne se déclenche jamais sur le corpus doit être testée sur un cas construit (un test avec des investissements réduits), sinon on ne sait pas si elle fonctionne.
- Je sais écrire un ratio avec sa définition, son format et ses gardes-fous
- Je sais pourquoi un levier négatif ne doit pas passer sous un seuil
- Je sais écrire une règle d'alerte qui cite les pages de ses sources
- Je sais expliquer pourquoi un ROE élevé peut coexister avec une trésorerie qui fond