Rules as Code : leçons tirées d’une expérience
Dit artikel is ook beschikbaar in het Nederlands.
Dans un article précédent, nous avons examiné Rules as Code, une approche visant à réduire l’écart entre les réglementations et les logiciels. Nous avons illustré qu’il existe de nombreux obstacles pratiques à surmonter, malgré l’objectif louable. L’encodage uniforme des règles avec leur historique, leurs interdépendances et leurs dépendances est un défi qui peut nécessiter un investissement considérable en termes de personnel et de ressources. De plus, une gestion active permanente est nécessaire pour prendre en compte chaque modification apportée aux règles. Même à petite échelle, une collaboration étroite entre juristes et développeurs est indispensable, car des décisions motivées devront régulièrement être prises en matière d’interprétation. En l’absence de normes industrielles et alors que les meilleures pratiques sont encore en cours d’élaboration, les early adopters risquent de devoir payer la pioneer tax. La complexité des compétences gouvernementales ne facilite pas une éventuelle application en Belgique.
Partiellement sous l’impulsion du rapport détaillé de l’OCDE de 2020, certaines administrations se sont déjà pleinement engagées dans l’élaboration de proof-of-concepts, parfois à grande échelle. Il existe donc aujourd’hui plusieurs cadres relativement matures. La France est sans aucun doute le pays pionnier ; l’initiative que nous allons présenter ci-dessous vient de France. Les Pays-Bas ne sont pas en reste : l’administration fiscale néerlandaise utilise depuis un certain temps déjà son propre langage de domaine, RegelSpraak, qu’elle interprète et traite à l’aide du rule engine ALEF. Cependant, le code source publié à ce sujet semble pour l’instant se concentrer davantage sur la méthodologie que sur les applications.
OpenFisca
OpenFisca a vu le jour en 2011 en tant que moteur de microsimulation open source permettant de convertir les règles fiscales et sociales (“tax & benefit system”) en code exécutable. Les effets de cette réglementation, et les éventuelles modifications, peuvent alors être simulés tant pour des cas individuels que pour des populations entières. Parmi les sites web utilisant OpenFisca en arrière-plan, on peut citer LexImpact (simulation des modifications de la législation socio-fiscale), Mes droits sociaux (simulation des droits sociaux) et 1jeune1solution (mesures de soutien diverses). Parmi les exemples étrangers, citons BenefitMe (Nouvelle-Zélande), Les meves ajudes (Barcelone) ou PolicyEngine (Royaume-Uni/États-Unis) – ces derniers ayant toutefois nécessité des modifications importantes du moteur.
Afin de modéliser notre propre système fiscal et/ou de sécurité sociale, nous devons créer une fourche du Template country OpenFisca générique. Plusieurs autres pays ont déjà expérimenté cette approche, comme le Sénégal, le Paraguay et la Tunisie, qui figurent dans la liste des repositories. La législation régionale ou locale peut être ajoutée grâce à des extensions pluginà un système national, comme celui de Paris. Une fois le repository initialisé, nous pouvons commencer à travailler sur ce qui pourrait un jour devenir openfisca-belgium. La modélisation dans OpenFisca se fait en écrivant des classes et des méthodes Python, qui représentent les entités, les variables et les formules de calcul issues de la réglementation.
Malheureusement, c’est à peu près là que s’arrête la partie facile. Le repository du country template est minimaliste et, bien qu’il existe une documentation avec un petit tutoriel pour créer sa propre version, celle-ci se concentre principalement sur les premières étapes. Les directives sur la manière de structurer au mieux notre propre fourche dès que le nombre de variables et de paramètres augmente font largement défaut. Le repository du projet parent openfisca-france peut certes servir d’exemple, mais il est très volumineux et ne permet pas vraiment de comprendre les raisons de leurs choix structurels ou architecturaux.
L’aspect d’une GUI ou d’une interface web reste également sous-estimé. Pourtant, l’interface de LexImpact simulator pour l’impôt sur le revenu en France, par exemple, est justement un point fort. Comme guide pour la construction d’une interface web, on se réfère à tutoriels et slides d’un workshop, où les premières étapes sont présentées dans Svelte, React et VueJS. Cependant, le fait qu’une GUI ou une application web doive encore être construite from scratch en plus d’une instance OpenFisca propre constitue un obstacle supplémentaire à l’adoption. La construction d’une GUI est en effet chronophage. Il serait utile de disposer de bibliothèques OpenFisca-GUI avec des composants réutilisables pour les principaux frameworks web, afin qu’un serveur OpenFisca puisse éventuellement être associé à une interface web générique par défaut. Un plugin Drupal semble actuellement être le seul à aller quelque peu dans cette direction.
L’IA à la rescousse ?
Étant donné qu’OpenFisca, Svelte, React et Vue sont tous nouveaux pour l’auteur, et que les outils d’IA promettent d’accélérer l’intégration des développeurs, nous saisissons l’occasion pour tester simultanément l’IDE Cursor alimenté par l’IA. Ce clone de Visual Studio Code est enrichi de la possibilité d’appeler des LLM (dans notre cas, basés sur le cloud public). De plus, des sélections issues de fichiers du projet peuvent être marquées comme contexte pour la question. Cursor peut fournir des suggestions d’ajouts ou de modifications à apporter aux fichiers qui, une fois approuvées, peuvent être directement intégrées dans la base de code.
Interagir avec des modèles d’IA comporte des risques pour la confidentialité. Cette expérience est principalement possible car nous travaillons avec du code open source, des réglementations publiées et leur documentation également publique, qui ne sont pas sensibles. Cependant, étant donné que tout ce qui se trouve dans l’IDE peut être envoyé au modèle de langage, nous devons toujours veiller à ne pas ouvrir dans l’IDE des fichiers contenant des identifiants, des clés API ou des informations personnelles. Cela reste la responsabilité de chaque développeur. Dans tous les cas, il est recommandé d’être prêt à faire tourner les clés API ou les credentials, car dans le feu de l’action d’un debugging, il est facile de partager trop d’informations avec un LLM.
Enfin, il convient de mentionner que cette expérience a été réalisée avec les versions 1.6 et 1.7 de Cursor en septembre-octobre 2025, avec le modèle de langage sous-jacent GPT-4.5 d’OpenAI, puis GPT-5.0, utilisé avec une clé API dédiée (et non via Cursor). Les versions ultérieures disposent de nombreuses fonctionnalités plus récentes (notamment des workflows agentic) et il est possible que l’expérience soit aujourd’hui (janvier 2026) très différente. Cependant, les principaux enseignements restent généralement valables pour tout développement alimenté par l’IA, que ce soit via IDE, ligne de commande ou les deux (par exemple, Anthropic Claude Code).
Dans un premier temps, nous ajoutons la documentation nécessaire à notre projet. Nous prenons comme exemple la loi du 26 mai 2002 concernant le droit à l’intégration sociale. Avec tous les autres arrêtés royaux, lois et circulaires pertinents, elle est clairement répertoriée sur le site web du SPP Intégration sociale. Afin de faciliter la recherche et l’interprétation du texte pour un LLM dans un IDE, nous l’enregistrons sous forme de fichier texte plat sans mise en forme, que nous ajoutons à un nouveau dossier contenant les sources pertinentes dans l’arborescence source du projet. Nous ne pouvons que supposer que cette approche est optimale, mais il faut bien commencer quelque part.
Entités
Les entités dans OpenFisca indiquent pour qui nous effectuons le calcul. Il peut s’agir d’individus, de familles ou d’autres groupes de personnes (entreprises, organisations, etc.). Ce sont les fondements sur lesquels nous pourrons ensuite spécifier des variables qui, ensemble, formeront une “situation” pour laquelle nous pourrons effectuer un calcul. Les entités Person et Household sont déjà présentes dans le code. Une question logique se pose donc : sur la base du texte de loi donné, pouvons-nous définir d’autres entités qui seraient utiles ?
Après avoir posé la question à GPT-5 dans Cursor, avec le texte de loi sélectionné comme contexte, il nous est proposé d’ajouter les entités suivantes :
- Eligible Person for Societal Integration
- Living Wage Recipient
- Employment Project Participant
Les modifications proposées au code sont syntaxiquement correctes. Cependant, aucune de ces trois modifications n’est utile ou nécessaire : dans les trois cas, il s’agit de variantes de Person. Les propriétés qui leur permettraient, par exemple, de percevoir un revenu d’intégration sont plutôt des variables ajoutées à l’entité Person déjà existante. La valeur de ces variables dépend en outre d’autres variables également liées à ce même individu, telles que les revenus du travail ou le statut d’invalidité. Les entités, qui servent principalement à des concepts autonomes, ne sont pas le bon choix dans ce cas.
En outre, GPT-5 semble avoir mal interprété le concept de “rôle” au sein d’une entité de groupe OpenFisca. Il tente de construire “Eligible Person for Societal Integration” avec différents “rôles” comme composants : “Belgian National”, “EU Citizen”, “Foreigner”, “Stateless”, “Refugee”… Sans doute parce que ces possibilités apparaissent à l’Art.3, 3°, de la loi. Dans OpenFisca, cependant, une entité de groupe est composée de personnes qui se voient chacune attribuer un rôle. Un Household comprend ainsi des rôles Adult et Child. Il est assez absurde qu’une EligiblePerson puisse comprendre plusieurs Foreigners. La nationalité ou l’origine, ou d’autres conditions fixées dans cette loi, sont également ici des variables liées à la personne, et non à une entité en soi.
À un autre moment, une entité distincte a été créée pour le CPAS. Bien qu’il semble logique de modéliser les CPAS et de les considérer comme une entité – ils sont en effet mentionnés dans la loi –, ce n’est pas (encore) le cas ici. Il n’existe en effet pas différents types de CPAS avec des caractéristiques ou des rôles différents, pour lesquels nous devrions effectuer des calculs différents à chaque fois. Dans le contexte de cette loi, où c’est le citoyen pour lequel nous calculons le droit à l’aide sociale, le CPAS est avant tout une donnée constante et invariable. Dans OpenFisca, nous pouvons donc pour l’instant ignorer cet aspect. (Un type d’entité “institut” n’est pas non plus prévu.)
Nous constatons ici que Cursor ne peut pas répondre “non” à la question de savoir si d’autres entités utiles peuvent être ajoutées. Il ne peut pas critiquer ou corriger de son propre chef le raisonnement qui sous-tend cette question. Tout au long de l’expérience, Cursor et GPT-5 ont également montré une tendance à la complexité inutile. Cela représente un risque important pour les développeurs qui travaillent avec du code ou des frameworks inconnus : si l’on suit trop rapidement ces suggestions, on risque de perdre le contrôle par la suite et de devoir apporter des corrections très difficiles aux fondements du projet. Une fois qu’une mauvaise voie a été empruntée, il s’avère également difficile de revenir en arrière et de faire oublier ces étapes. Surtout si on les a d’abord acceptées par ignorance, elles s’inscrivent dans le contexte et sont reprises dans les questions suivantes. Ce “context rot” insidieux est désormais un problème bien connu et une cause importante de perte de temps avec le AI enabled coding.
Variables
Le cœur du modèle réside dans les variables qui représentent les droits et les conditions prévus par la loi. L’article 2 de la loi énumère les différentes formes d’intégration sociale auxquelles une personne peut avoir droit (notamment l’emploi, le revenu d’intégration, le projet individualisé). L’article 3 contient les conditions qu’une personne doit remplir pour exercer ce droit. Nous avons transposé ces dispositions étape par étape dans le code.
Dans la pratique, le droit à l’intégration sociale signifie qu’un CPAS doit soutenir une personne par le biais (1) d’un emploi ou d’une formation, (2) d’un revenu d’intégration, ou (3) d’un projet individualisé d’intégration sociale. Cela peut se traduire par trois variables booléennes sur l’entité Personne, par exemple employment_right, living_wage_right et individualized_project_right. Cursor fournit ici une suggestion de code pertinente et propose une formule placeholder simple : tant qu’une personne “est éligible à l’intégration” (une autre variable), le droit s’applique. Nous obtenons ainsi la définition suivante de employment_right :
class employment_right(Variable):
value_type = bool
entity = Person
definition_period = MONTH
def formula(person, period, parameters):
return person("eligible_for_integration", period)Le contenu de cette formule placeholder est abordé dans l’article 3 ci-dessous. Celui-ci modélise les conditions suivantes pour être éligible :
- Séjour en Belgique (selon les règles à déterminer par arrêté royal).
- Âge : la personne est majeure (18 ans et plus) ou, si elle est mineure, assimilée à une personne majeure selon les exceptions prévues par la présente loi.
- Nationalité ou statut de séjour : la personne est belge, citoyenne de l’UE (après 3 mois de séjour), étrangère enregistrée, apatride, réfugiée ou bénéficiaire d’une protection subsidiaire.
- Ressources insuffisantes
- Disposition à travailler (sauf si cela est impossible pour des raisons de santé ou d’équité).
- Épuisement des droits issus d’autres régimes
Toutes ces conditions sont regroupées dans une variable booléenne centrale societal_integration_right. Cette variable indique si une personne, compte tenu de sa situation personnelle, peut prétendre à l’intégration sociale. En fait, il s’agit de la traduction de “la personne remplit-elle toutes les conditions de l’article 3 ?“. La formule combine toutes les sous-conditions :
class societal_integration_right(Variable):
value_type = bool
entity = Person
definition_period = MONTH
label = "Right to societal integration"
def formula(person, period, parameters):
residency = person("residency_status", period)
is_major = person("is_major", period)
nationality = person("nationality_status", period) in ["belgian", "eu_citizen", "registered_foreigner", "stateless", "refugee"]
insufficient_income = not person("has_sufficient_income", period)
willing_to_work = person("willing_to_work", period)
claiming_benefits = person("claiming_benefits", period)
return (residency and is_major and nationality and insufficient_income and willing_to_work and claiming_benefits)Notons ici quelques lacunes étranges dans la suggestion de Cursor. Ainsi, le nom de la variable societal_integration_right n’est pas identique au placeholder eligible_for_integration défini précédemment, alors que c’était pourtant l’intention. En outre, la condition de nationalité omet tout simplement la possibilité d’une protection subsidiaire. Enfin, la sixième condition, qui stipule que la personne doit d’abord faire valoir ses droits à d’éventuelles prestations sociales, est mentionnée de manière très rudimentaire sous le nom de claiming_benefits, un nom de variable qui ne reflète pas vraiment ce qui est visé.
Nous pouvons donc accepter cette suggestion, mais nous sommes immédiatement obligés d’apporter trois corrections. Nous pouvons facilement détecter la non-conformité du nom de la variable, car les tests ne fonctionneront pas s’il reste des variables non déclarées dans le code. Un élément manquant dans la formule, tel qu’une condition oubliée, est cependant beaucoup plus facile à négliger et, s’il n’est pas détecté, entraîne à coup sûr des erreurs d’exécution. Nous constatons donc ici la nécessité de se référer au texte de loi afin de vérifier que le code généré correspond bien à ce que dit le texte de loi. Cette vérification doit être effectuée avec suffisamment d’attention pour pouvoir identifier les appellations malheureuses ou les interprétations subtiles erronées du texte.
Les corrections éventuelles doivent également être effectuées le plus rapidement possible. Si un code erroné reste présent dans l’éditeur, il fera en effet partie du contexte utilisé par le modèle d’IA et servira lui-même de base pour les suggestions suivantes. Cela peut conduire à une situation où l’on continue à recevoir des suggestions contenant toujours les mêmes erreurs, qu’il faut donc corriger à chaque fois, ce qui n’est pas propice à la productivité.
Les variables utilisées dans la méthode formula() de societal_integration_right ci-dessus doivent bien sûr être définies à leur tour : pour chacune de ces variables, nous devons écrire une classe. Cela peut donner lieu à des chaînes complexes de dépendances. Ainsi, is_major pourrait être une simple variable d’entrée booléenne, mais nous pouvons également la calculer sur la base de la date du jour et d’une nouvelle variable birthdate. Le calcul de la formule des variables peut également utiliser les paramètres d’une loi – ainsi, en Belgique, la majorité n’est atteinte qu’à partir de 18 ans depuis le 1er mai 1990. Cela nous ramènerait alors au Code civil et à son histoire – pour rester concis, nous n’approfondirons pas cette question pour l’instant.
Dernière remarque : le modèle tel qu’il est construit ici est bien sûr une représentation simplifiée. Notez toutefois que même dans ce cas, après seulement trois articles de loi, nous avons déjà défini une dizaine de classes Python, avec la possibilité d’en ajouter d’autres si nous souhaitons approfondir le sujet. Cursor et GPT-5 écrivent un code relativement verbeux, avec de nombreuses variables et méthodes auxiliaires, qui pourraient parfois être simplifiées. Certains détails de la loi, tels que le délai d’attente de trois mois pour les citoyens de l’UE ou les exceptions qui existent pour certaines catégories de mineurs (art. 7), nécessiteraient de nombreuses variables ou conditions supplémentaires dans un modèle complet.
IA et code : quelques pièges
En ce qui concerne les meilleures pratiques pour l’utilisation de l’IA dans le cadre de tels projets, nous identifions encore quelques pièges, en plus de ceux que nous avons déjà mentionnés.
Ajouter trop de documentation au début conduit rapidement à une “confusion contextuelle“, dans laquelle les suggestions ou les réponses du LLM sont basées sur des informations qui ne sont pas (encore) pertinentes. Il est préférable d’ajouter la documentation progressivement, au fur et à mesure que la fonctionnalité se développe, plutôt que d’ajouter l’analyse complète et le contexte à l’IDE dès le départ. Dans le cas de la réglementation, ajoutez les règles article par article à l’IDE, au fur et à mesure de l’avancement du projet, et résistez à la tentation d’intégrer à l’avance l’ensemble du texte de loi dans l’IDE en tant que “référence encyclopédique”.
Le “context rot” ou “context poisoning” survient lorsque l’IA s’engage dans une mauvaise voie, s’y enlise et finit par oublier des informations plus pertinentes, ce qui rend la récupération plus difficile. Le “context quarantining“, qui consiste à diviser le problème en sous-problèmes plus petits, chacun avec son propre contexte, est un remède logique à ce problème. C’est également la voie empruntée par la plupart des systèmes de “deep research” ou “multi agentic“. Dans un IDE, cela impliquerait qu’un système d’IA devrait segmenter la base de code et la documentation à partir d’une certaine taille. La mise en œuvre technique de cette solution en arrière-plan semble être un défi de taille, et différents IDE développeront probablement leur propre approche à cet égard dans un avenir proche.
Une autre source de frustration était que l’IA plaçait parfois le code ou les fichiers au mauvais endroit ou supposait que certaines choses existaient. Par exemple, les formules générées faisaient référence à des variables qui n’étaient pas encore définies. Cela génère bien sûr des messages d’erreur lors des tests. Nous devions alors ajuster l’IA ou insérer nous-mêmes des variables supplémentaires pour couvrir ces références. Même des détails mineurs, tels que le formatage de la documentation ou la création ou non des importations nécessaires, nécessitaient parfois une correction manuelle. Ce type d’incohérences démontre qu’il n’est pas possible de se fier aveuglément aux suggestions de l’IA. Un développeur doit constamment vérifier si le code généré correspond à l’intention et, dans le cas contraire, intervenir immédiatement.
publi.codes
Nous souhaitons également signaler l’existence de publi.codes comme alternative possible à OpenFisca. Plus récent et plus moderne, ce dernier exige que les règles soient codées au format YAML, ce qui est beaucoup plus pratique que l’écriture de sous-classes en Python et beaucoup plus lisible pour les non-développeurs. En contrepartie, on est toutefois limité aux opérations autorisées par le moteur sous-jacent. Ce n’est qu’à partir de la version 2, encore en cours de développement, que des possibilités d’encoder des barèmes ou des abattements (montants exonérés), très fréquents en Belgique, seront ajoutées.
La version actuelle de publi.codes dépend en outre de l’écosystème NPM, qui est actuellement régulièrement affecté par des attaques du supply chain. Publi.codes v2 serait quant à lui compilé vers OCaml, un langage de programmation que nous n’utilisons pas chez Smals. Étant donné qu’il y a peu de chances que Smals souhaite introduire ce langage de programmation dans son portefeuille (et mettre en place une équipe de support à cet effet), il ne semblait pas très utile d’examiner publi.codes en profondeur dans le cadre de cet exercice. Il convient toutefois de noter que publi.codes dispose de quelques libraries prêtes à l’emploi en matière de composants UI.
Conclusion
OpenFisca et publi.codes sont deux systèmes particulièrement performants lorsque les règles peuvent être modélisées sous forme de calculs explicites et testables. Ils sont moins adaptés aux réglementations qui font appel aux décisions discrétionnaires, à la libre interprétation, aux exceptions sans paramètres clairs ou aux workflows de “case management”. Il s’agit avant tout de systèmes de calcul et de règles, et non de plateformes de traitement de dossiers. Ils peuvent donc éventuellement servir de moteur pour des applications capables de calculer les impôts ou les allocations au niveau de la personne/du ménage (droit à quelque chose + montant), ou pour simuler l’impact politique d’éventuelles modifications (“combien coûte cette réforme ?”, “qui y gagne/y perd ?”). Cela peut être intéressant tant pour les législateurs que pour les citoyens.
Cependant, un projet OpenFisca ne se met pas en place rapidement. Sur le plan conceptuel, OpenFisca est quelque peu déroutant pour un développeur : bien qu’OpenFisca utilise des classes Python, celles-ci ne servent pas à modéliser des objets, mais à enregistrer de manière déclarative des entités, des variables et des règles de calcul issues de la réglementation. Étant donné que par variable, il faut écrire une classe et que des dizaines de variables peuvent facilement entrer en jeu dans un article de loi complexe, on se retrouve avec une pile de code qui s’accumule rapidement et qui est difficile à organiser de manière claire. En outre, le développement d’une interface graphique nécessite également beaucoup de travail supplémentaire. Le projet manque encore de tooling nécessaire pour atténuer ces problèmes récurrents. (Bien sûr, cela n’aide pas lorsque l’administration publique, qui semble considérer que les projets open source peuvent par définition s’autofinancer, décide soudainement de fermer les vannes en 2020.
Enfin, nous pouvons ajouter que cette expérience a été à la fois un reality check utile et instructif sur ce que les LLM peuvent apporter, et gâcher, à un environnement de travail de développeur. Le meilleur conseil reste de garder les rênes fermement en main et de travailler par petites étapes incrémentielles. Certains outils d’IA seront plus performants que d’autres dans divers domaines. Donner des réponses négatives ou détecter des erreurs dans les questions reste un défi pour les LLM, ce qui comporte certains risques. Cependant, l’assistance IA dans les IDE évolue rapidement, et une expérience similaire se déroulera sans doute différemment l’année prochaine.
Rules As Code ne signifie certainement pas qu’aujourd’hui, nous pouvons fournir un texte de loi à une IA pour qu’elle déploie un programme. Cependant, dans les années à venir, les forums spécialisés accorderont sans aucun doute une grande attention à l’interaction entre la loi, la mise en œuvre et les outils d’IA. Pour l’instant, la complexité de la réglementation elle-même, même avec une IA de plus en plus performante, reste le principal obstacle aux projets Rules As Code.
______________________
Cette contribution a été soumise par Joachim Ganseman, consultant IT chez Smals Research. L’article a été rédigé en son nom propre et ne prend pas position au nom de Smals.