openfisca-france icon indicating copy to clipboard operation
openfisca-france copied to clipboard

Contribution exceptionnelle sur les hauts revenus (CEHR) et arrondi

Open PiGo86 opened this issue 4 years ago • 8 comments

Bonjour,

Avocat fiscaliste et codant en Python à mes heures perdues, j'ai commencé à effectuer quelques tests sur OpenFisca.

Avant toute chose, félicitations pour ce travail, c'est véritablement impressionnant.

Pour une première approche, je souhaitais soulever une anomalie qui reste somme toute mineure (mais qui pourrait le cas échéant me permettre de soumettre un premier commit).

Qu'ai-je fait ?

En voulant tester un calcul de la contribution exceptionnelle sur les hauts revenus (CEHR), j'ai lancé la simulation suivante, représentant le cas d'un contribuable célibataire, ayant un revenu fiscal de référence de 600 001 € en 2020 :

from openfisca_core.simulation_builder import SimulationBuilder
from openfisca_france import FranceTaxBenefitSystem

TEST_CASE = {
    "individus": {"d1": {}},
    "foyers_fiscaux": {"ff1": {"declarants": ["d1"],
                               "rfr": {"2020": 600_001},
                               }},
    }

tax_benefit_system = FranceTaxBenefitSystem()

simulation_builder = SimulationBuilder()
simulation = simulation_builder.build_from_entities(tax_benefit_system, TEST_CASE)

test = simulation.calculate('cehr', '2020')

À quoi m'attendais-je ?

S'agissant d'un contribuable célibataire, le taux de la CEHR est de 3 % pour la fraction de revenu fiscal comprise entre 250 000 € et 500 000 €, et de 4 % au-delà, soit une CEHR de 11 500 € pour un revenu fiscal de référence de 600 001 € (arrondi à l'euro le plus proche, c'est ce point qui est important ici).

Que s'est-il passé en réalité ?

La simulation renvoie une CEHR de 11 500,04 € :

test # Renvoie array([11500.04], dtype=float32)

En regardant la classe cehr, je constate que l'attribut de classe value_type a pour valeur float. Initialement, je pensais naïvement qu'une correction possible était de modifier cette valeur pour int. Toutefois, si au cas présent, on obtiendrait bien le résultat attendu, elle pourrait dans d'autres cas poser des problèmes d'arrondi.

Ce comportement vis-à-vis des arrondis est-il souhaité, ou à tout le moins connu ? Dans le cas où ce comportement mériterait d'être corrigé, à quel niveau pensez-vous que cela devrait être fait pour respecter la philosophie générale du projet ? Directement au niveau de la méthode formula de la classe cehr, ou à un autre endroit ?

Contexte

Je m'identifie plus en tant que :

  • [x] Contributeur·e : je contribue à OpenFisca France.
  • [ ] Développeur·e : je crée des outils qui utilisent OpenFisca France.
  • [ ] Économiste : je réalise des simulations avec des données.
  • [ ] Mainteneur·e : j'intègre les contributions à OpenFisca France.
  • [ ] Autre : (ajoutez une description du contexte dans lequel vous utilisez OpenFisca).

PiGo86 avatar May 06 '20 22:05 PiGo86

@PiGo86 : merci pour vos gentils mots.

Pour être franc, la précision au niveau des arrondis d'euro n'a jamais été une priorité au regard des usages actuels. Mais il n'y a aucune raison de ne pas être plus précis. Et nous serons ravis d'accepter votre contribution.

Je ne suis pas sûr que convertir le type en int fasse l'affaire mais cela vaut le coup d'essayer. Une autre solution serait d'arrondir explicitement en utilisant les fonctions numpy adaptée (je pense à rint, round, car ̀trunc ceiloufloor` ne semblent pas faire l'affaire).

@maukoquiroga @sandcha @Morendil : des conseils sur ce point ?

benjello avatar May 07 '20 07:05 benjello

@benjello : merci beaucoup pour votre retour.

Effectivement, après avoir poussé un peu plus mes investigations, je constate que souvent, il n'y a pas de gestion d'arrondis pour l'impôt sur le revenu.

Lorsque des arrondis sont utilisés (c'est le cas par exemple de l'abattement forfaitaire de 10 % pour frais professionnels applicables sur les traitements et salaires), il est fait usage de la fonction round de numpy.

Lorsqu'elle est appliquée sur un nombre dont la décimale est 0,5, cette fonction arrondit vers l'entier pair le plus proche (ainsi, 3,5 et 4,5 sont tous deux arrondis à 4).

Or, en matière d'impôts directs, la règle générale de gestion des arrondis est fixée par l'article 1657 du CGI, qui prévoit que que les bases d'imposition et les cotisations calculées sur ces bases sont arrondies à l'euro le plus proche, la fraction égale à 0,50 € étant arrondie à l'euro supérieur.

L'article 193 du CGI, spécifique à l'impôt sur le revenu, reprend la même règle, en précisant qu'elle s'applique au revenu imposable ainsi qu'aux différents éléments ayant concouru à sa détermination.

Ainsi, dans OpenFisca, les différentes variables concourant à la détermination du revenu imposable du foyer fiscal devraient être arrondies selon ces règles.

Et l'usage de round n'est donc pas conforme, puisque les montants calculés ayant une décimale égale à 0,5 doivent être systématiquement arrondis à l'euro supérieur.

Après quelques recherches, il semble que l'on pourrait obtenir ce comportement à l'aide de la fonction floor de numpy, par exemple (à adapter en présence de montants négatifs) :

def arrondi_fiscal(n):
    return numpy.floor(n+0.5)

Je vous propose donc de modifier les différentes variables concernées pour procéder à cet arrondi, et de modifier leur type pour int (au lieu de float).

Auriez-vous une suggestion de fichier dans lequel définir une telle fonction d'arrondi ?

PiGo86 avatar May 07 '20 21:05 PiGo86

Merci @PiGo86 : cette contribution est bienvenue.

Si les variables modifiées sont toutes dans le même fichier je mettrais une petite section helper(s) à son extrémité.

# Helper

def arrondi_fiscal(n):
    return numpy.floor(n+0.5)

Si les variables sont dispersées dans plusieurs fichiers, je les mettrais au niveau du module (dans le fichier (__init__.py) le plus profond qui les contient toutes.

benjello avatar May 08 '20 09:05 benjello

@PiGo86 Merci pour ces retours ! Nous avons déjà traité des questions d'arrondis précédemment, cf. #1198 - c'est un sujet épineux, on avait pu constater que les règles d'arrondi appliquées par les simulateurs de la DGFIP semblent changer d'une année sur l'autre. (Est-il possible que les textes de loi disent une chose et les algorithmes de calcul une autre ? Hélas oui…) Nous avions opté pour nous rapprocher le plus possible des calculs effectués par la DGFIP, en les considérant comme des "oracles" pour nos propres cas de test.

Morendil avatar May 10 '20 11:05 Morendil

Je n'avais en effet pas vu ce précédent sujet sur la question. Après avoir revu le code et fais quelques séries de tests, le sujet se révèle en effet assez complexe.

J'essaie toutefois d'effectuer des modifications sur le fichier model/prelevements_obligatoires/impot_revenu/ir.py, pour appliquer ce fameux arrondi fiscal.

Je bute sur un point ; par exemple, dans la classe suivante :

class ir_brut(Variable):
    value_type = float
    entity = FoyerFiscal
    label = "Impôt sur le revenu brut avant non imposabilité et plafonnement du quotient"
    definition_period = YEAR

    def formula(foyer_fiscal, period, parameters):
        nbptr = foyer_fiscal('nbptr', period)
        taux_effectif = foyer_fiscal('taux_effectif', period)
        rni = foyer_fiscal('rni', period)
        bareme = parameters(period).impot_revenu.bareme

        return (taux_effectif == 0) * nbptr * bareme.calc(rni / nbptr) + taux_effectif * rni

Pourriez-vous m'indiquer où est définie la méthode calc appelée sur la variable bareme ? Je comprends que cette méthode applique le barème progressif selon les taux et les tranches définies dans le fichier irpp.yaml. Mais j'aimerais pouvoir regarder la méthode utilisée pour arrondir (ou non) le montant obtenu.

PiGo86 avatar May 10 '20 21:05 PiGo86

Elle est définie ici.

benjello avatar May 10 '20 21:05 benjello

Bonjour @PiGo86, avez-vous pu continuer votre investigation sur le sujet ?

Il me semble qu'il serait préférable d'appliquer l'arrondi dans la formule elle-même, car la fonction de calcul des barèmes fait partie de l'infrastructure commune aux modèles socio-fiscaux de tous les pays.

Aussi, comme évoqué par @Morendil, ça vaudrait le coup de vérifier l'arrondi effectué par le simulateur de la DGFiP.

bonjourmauko avatar Nov 11 '20 15:11 bonjourmauko

Bonjour @maukoquiroga,

Malheureusement je n'ai pas avancé depuis quelques temps sur ce sujet.

J'avais commencé à appliquer la règle d'arrondi décrite ci-dessus sur pas mal de catégories d'imposition à l'IR, et j'obtenais, au vu des tests, des résultats cohérents.

Cependant, ça impliquait une quantité non négligeable de modifications, et après coup, je ne pense pas que cela soit très pertinent pour une première contribution...

Donc je pense pouvoir faire prochainement une proposition de fusion, bien plus modeste, sur la seule partie du code afférente à la CEHR, ce serait déjà un bon début.

PiGo86 avatar Nov 11 '20 17:11 PiGo86