درس 1 — Cadrer

Le problème, le corpus et l'architecture

8 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Le problème, le corpus et l'architecture

Un fonds de dette privée demande une première lecture de trois sociétés avant un comité. L'analyste qui fait ce travail à la main commet toujours les mêmes fautes : il prend la colonne 2024 pour 2025 parce que l'ordre des colonnes change d'un tableau à l'autre, il recopie 184,2 sans voir que le tableau est en millions, il résume une note d'annexe au lieu de la citer. Un LLM branché naïvement sur les PDF reproduit ces trois fautes, plus une quatrième : il calcule de tête et se trompe en silence.

Le mécanisme : séparer ce qui se lit de ce qui se calcule

La décision d'architecture qui structure tout le projet : le modèle ne produit jamais un chiffre qu'il n'a pas lu. Il transcrit des montants tels qu'imprimés, il recopie des citations, il rédige à partir de faits fournis. Tout le reste est du Python.

flowchart TB
  subgraph LLM["Ce que fait le modèle"]
    A["transcrire les tableaux"]
    B["repérer et citer les signaux"]
    C["rédiger le mémo"]
  end
  subgraph PY["Ce que fait Python"]
    D["convertir M€ en k€, normaliser les signes"]
    E["12 contrôles comptables"]
    F["16 ratios, 8 règles d'alerte"]
    G["vérifier chaque citation sur sa page"]
    H["vérifier chaque nombre du mémo"]
  end
  A --> D --> E --> F
  B --> G
  F --> C
  G --> C
  C --> H

Chaque flèche qui sort du modèle entre dans une vérification. C'est ce qui rend le système auditable : quand un chiffre est faux, on sait si l'erreur vient de la lecture (le modèle) ou du calcul (le code, donc un test).

TâcheModèle ou PythonPourquoi
Lire « (12 450) » dans un tableaumodèlela mise en page varie, le modèle s'y adapte
Transformer (12 450) en -12 450 et M€ en k€Pythonune règle fixe, testable, sans variance
Vérifier actif = passifPythonune égalité, pas une opinion
Calculer la marge d'EBITDAPythonle modèle arrondit, inverse, invente
Trouver la phrase sur le covenantmodèlelecture du langage naturel
Prouver que la phrase existe page 7Pythoncomparaison de chaînes

Un collègue propose de demander directement au modèle « calcule le ROE et le levier de chaque société », en arguant que Gemini sait faire des divisions. Quelle objection l'emporte ?

Le corpus : trois sociétés, des pièges réels

On ne valide pas un extracteur sur des documents qu'on n'a pas annotés. Le projet génère donc son corpus : trois rapports annuels de 8 pages (couverture, message de la direction, chiffres clés, compte de résultat, bilan, flux de trésorerie, notes annexes, facteurs de risque), et pour chacun un fichier d'annotations qui sert de vérité terrain.

SociétéProfilPièges dans le PDF
Helvane Robotique SArobotique industrielle, croissance de 12 %, trésorerie nettetableaux en M€ à une décimale
Maison Ferlac SASplats cuisinés, marge laminée, levier de 5,5xpage des flux scannée (image sans texte), EBITDA « ajusté » en page chiffres clés, covenant sur un indicateur ajusté
Solvéa Énergies SAphotovoltaïque, croissance de 45 %, free cash-flow très négatifbilan aux colonnes inversées (2024 puis 2025), changement de méthode comptable, parties liées

Les états financiers sont dérivés d'hypothèses (chiffre d'affaires, charges, investissements, niveaux de BFR, dette), ce qui les rend cohérents par construction : le bilan de 2025 découle de celui de 2024 et des flux de l'année.

files/analyste/modele_comptable.py
        var_bfr = -(bfr - bfr_prec)  # impact trésorerie : négatif si le BFR augmente
        cfo = rn + h.dotations_amortissements + var_bfr
        cfi = -h.investissements
        cff = h.variation_dettes - h.dividendes_verses + h.augmentation_capital
        tres = precedent["tresorerie"] + cfo + cfi + cff
        ...
        assert bilan["total_actif"] == bilan["total_passif"], (s.id, annee)

L'assert en fin de boucle garantit qu'aucune hypothèse modifiée ne produit un bilan déséquilibré : si vous ajoutez une société, le générateur refuse de produire un rapport faux. Le code complet : modele_comptable.py et generation.py.

Mise en œuvre

cd files
make setup                 # .venv, dépendances, .env copié depuis .env.example
.venv/bin/python run.py generer
make test

Résultat obtenu (2026-10-15)

INFO rapport généré : rapports/helvane-rapport-annuel-2025.pdf
INFO rapport généré : rapports/ferlac-rapport-annuel-2025.pdf
INFO rapport généré : rapports/solvea-rapport-annuel-2025.pdf
$ make test
..............                                                           [100%]
15 passed, 1 warning in 1.77s

Le rapport de Ferlac pèse 110 Ko contre 11 Ko pour les deux autres : la page 6 est une image JPEG.

Page 6 du rapport de Maison Ferlac : le tableau des flux est une image scannée, légèrement inclinée, sans couche texte

Ce qui casse en production

SymptômeCauseParade
Ratios faux sur une seule société, extraction « réussie »unité mal lue (M€ pris pour k€)le modèle déclare l'unité, Python convertit ; ajoutez un contrôle de vraisemblance (CA rapporté à l'effectif, par exemple)
Croissance négative alors que l'activité progressecolonnes inversées dans un tableauinstruction explicite « lis l'en-tête de chaque colonne » et champ annee par exercice
Valeurs plausibles mais fausses sur une pagepage scannée sans couche textedétection des pages vides et lecture visuelle (leçon 2)
Le corpus de test passe à 100 %, la production noncorpus trop propreajouter des rapports réels annotés et mesurer séparément

Règle de décision

Utilisez ce découpage modèle/Python dès qu'un chiffre produit peut engager une décision. Ne l'utilisez pas pour une synthèse exploratoire où l'ordre de grandeur suffit : un seul agent avec le PDF en entrée coûte moins cher à écrire. Surveillez le ratio entre champs lus et champs vérifiés : un champ qui n'entre dans aucun contrôle est un champ que personne ne relit.

Pratique

Vous ajoutez une quatrième société au générateur et l'assert « total_actif == total_passif » échoue. Que concluez-vous ?

Le responsable des risques veut que la vérité terrain soit saisie par un analyste plutôt que générée. Quand a-t-il raison ?

Exercice. Ajoutez dans modele_comptable.py une quatrième société fictive, « Ostral Logistique SAS » (entreposage frigorifique, 95 000 k€ de chiffre d'affaires, levier proche de 4x), avec deux exercices. Générez les rapports et vérifiez que l'assert passe.

Voir la solution

Il faut un Societe(...) dans SOCIETES, une entrée dans TEXTES (pages 2, 7 et 8) et dans SIGNAUX de generation.py. Pour viser un levier de 4x, partez de l'EBITDA (résultat d'exploitation + dotations) : avec 95 000 k€ de CA et une marge d'EBITDA de 12 %, l'EBITDA vaut 11 400 k€ ; la dette nette visée est donc d'environ 45 600 k€. Comme la trésorerie est calculée (c'est la variable d'équilibre), ajustez dettes_financieres du bilan initial et variation_dettes jusqu'à obtenir ce niveau, puis contrôlez :

.venv/bin/python -c "from analyste.modele_comptable import *; from analyste.ratios import *
s=[x for x in SOCIETES if x.id=='ostral'][0]; print(calculer_ratios(calculer_etats(s))[2025]['levier'])"
.venv/bin/python run.py generer

Si l'assert tres > 0 échoue, vos flux consomment plus de trésorerie qu'il n'en existe : augmentez la dette ou baissez les investissements. Les tests test_verite_terrain_coherente couvrent automatiquement la nouvelle société.

  • Je sais expliquer pourquoi le modèle transcrit et ne calcule pas
  • Je sais générer le corpus et ses annotations
  • Je connais les quatre pièges du corpus et ce qu'ils testent
  • make test est vert sur mon poste
التالي