Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permet inversion au niveau mensuel #252

Merged
merged 11 commits into from
May 30, 2024
66 changes: 46 additions & 20 deletions openfisca_france_data/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import logging

from openfisca_core import periods
from openfisca_core.periods.date_unit import DateUnit
from openfisca_core.taxscales import MarginalRateTaxScale, combine_tax_scales
from openfisca_core.formula_helpers import switch
from openfisca_france.model.base import TypesCategorieSalarie, TAUX_DE_PRIME
from openfisca_france.model.prelevements_obligatoires.prelevements_sociaux.cotisations_sociales.base import (
cotisations_salarie_by_categorie_salarie,
)
from openfisca_france_data.smic import smic_horaire_brut
from openfisca_france_data import openfisca_france_tax_benefit_system


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -58,7 +60,7 @@ def create_salaire_de_base(individus, period = None, revenu_type = 'imposable',
taux_abattement = parameters_csg_deductible.abattement.rates[0]
try:
seuil_abattement = parameters_csg_deductible.abattement.thresholds[1]
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours était limité à 4 PSS
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours été limité à 4 PSS
seuil_abattement = None
csg_deductible = MarginalRateTaxScale(name = 'csg_deductible')
csg_deductible.add_bracket(0, taux_csg * (1 - taux_abattement))
Expand All @@ -72,7 +74,7 @@ def create_salaire_de_base(individus, period = None, revenu_type = 'imposable',
taux_abattement = parameters_csg_imposable.abattement.rates[0]
try:
seuil_abattement = parameters_csg_imposable.abattement.thresholds[1]
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours était limité à 4 PSS
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours été limité à 4 PSS
seuil_abattement = None
csg_imposable = MarginalRateTaxScale(name = 'csg_imposable')
csg_imposable.add_bracket(0, taux_csg * (1 - taux_abattement))
Expand All @@ -84,7 +86,7 @@ def create_salaire_de_base(individus, period = None, revenu_type = 'imposable',
taux_abattement = parameters_crds.abattement.rates[0]
try:
seuil_abattement = parameters_crds.abattement.thresholds[1]
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours était limité à 4 PSS
except IndexError: # Pour gérer le fait que l'abattement n'a pas toujours été limité à 4 PSS
seuil_abattement = None
crds = MarginalRateTaxScale(name = 'crds')
crds.add_bracket(0, taux_csg * (1 - taux_abattement))
Expand Down Expand Up @@ -121,13 +123,13 @@ def create_salaire_de_base(individus, period = None, revenu_type = 'imposable',
whours = parameters.marche_travail.salaire_minimum.smic.nb_heures_travail_mensuel

if period.unit == 'year':
plafond_securite_sociale = plafond_securite_sociale_mensuel * 12
heures_temps_plein = whours * 12
nb_mois = 12
elif period.unit == 'month':
plafond_securite_sociale = plafond_securite_sociale_mensuel * period.size
heures_temps_plein = whours * period.size
nb_mois = period.size
else:
raise
plafond_securite_sociale = plafond_securite_sociale_mensuel * nb_mois
heures_temps_plein = whours * nb_mois

if revenu_type == 'imposable':
salaire_pour_inversion = individus.salaire_imposable
Expand All @@ -152,11 +154,10 @@ def create_salaire_de_base(individus, period = None, revenu_type = 'imposable',
)

def add_agirc_gmp_to_agirc(agirc, parameters):
plafond_securite_sociale_annuel = parameters.prelevements_sociaux.pss.plafond_securite_sociale_mensuel * 12
salaire_charniere = parameters.prelevements_sociaux.regimes_complementaires_retraite_secteur_prive.gmp.salaire_charniere_annuel / plafond_securite_sociale_annuel
cotisation = parameters.prelevements_sociaux.regimes_complementaires_retraite_secteur_prive.gmp.cotisation_forfaitaire_mensuelle.part_salariale * 12
n = (cotisation + 1) * 12
agirc.add_bracket(n / plafond_securite_sociale_annuel, 0)
salaire_charniere = parameters.prelevements_sociaux.regimes_complementaires_retraite_secteur_prive.gmp.salaire_charniere_annuel * (nb_mois / 12) / plafond_securite_sociale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le paramètre plafond_securite_sociale n'est donc plus défini dans la fonction ce qui va poser problème si on appelle la fonction. Idem pour nb_mois. Je les mettrai a minima en paramètre de la fonction

cotisation = parameters.prelevements_sociaux.regimes_complementaires_retraite_secteur_prive.gmp.cotisation_forfaitaire_mensuelle.part_salariale * nb_mois
n = (cotisation + 1) * 12 # pour permettre la mensualisation en cas d'inversion, en évitant un taux 12 fois plus élevé sur une tranche 12 fois plus étroite
agirc.add_bracket(n / plafond_securite_sociale, 0)
agirc.rates[0] = cotisation / n
agirc.thresholds[2] = salaire_charniere

Expand Down Expand Up @@ -209,7 +210,7 @@ def add_agirc_gmp_to_agirc(agirc, parameters):
def create_traitement_indiciaire_brut(individus, period = None, revenu_type = 'imposable',
tax_benefit_system = None):
"""
Calcule le tratement indiciaire brut à partir du salaire imposable ou du salaire net.
Calcule le traitement indiciaire brut à partir du salaire imposable ou du salaire net.
Note : le supplément familial de traitement est imposable. Pas géré
"""
assert period is not None
Expand Down Expand Up @@ -384,9 +385,21 @@ def create_traitement_indiciaire_brut(individus, period = None, revenu_type = 'i
1: brut_proratise * (heures_remunerees_volume / (heures_temps_plein)),
}
)
traitement_indiciaire_brut += (
(categorie_salarie == TypesCategorieSalarie[categorie].index) * brut
)

if period.start.year>2017:
parametre = parameters.cotsoc.cotisations_employeur[categorie]
if categorie == "public_titulaire_etat":
liste_cotis = ["famille", "ati", "financement_organisations_syndicales", "csa", "fnal_contributions_plus_de_20_salariés"]
else:
liste_cotis = ["famille", "atiacl", "financement_organisations_syndicales", "csa", "fnal_contributions_plus_de_20_salariés"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sylvainipp la CI ne tourne pas, l'erreur est que la variable fnal_contributions_plus_de_20_salaries n'est pas trouvé. J'ai regardé et je ne la vois effectivement pas dans Openfisca France. Donc a priori c'est un problème indépendant des données.

Je constate que tu as un accès directement à notre dépôt avec CI si jamais ça peut t'aider de regarder : https://git.leximpact.dev/benjello/openfisca-france-data/-/jobs/54501

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La variable est créée dans preprocessing, je ne comprends pas trop le problème. Après, la plus grosse partie de la correction vient de l'ajout de l'indemnité elle-même dans l'inversion, la prise en compte de l'exonération de cotisations sur cette indemnité est assez faible en termes de montant (10 euros par an max pour les fonctionnaires concernés). Comme c'est aussi la partie sur laquelle je suis le moins sûr, je l'enlève pour cette PR, et ne laisse donc que la correction de l'effet direct de l'indemnité. La Ci leximpact semble apprécier cette solution ;)

taux_indemnite_exo_cotisation = sum([parametre[cotis].rates[0] for cotis in liste_cotis]) # L'indemnité compensatrice de csg n'est pas soumise à la plupart des cotisations
traitement_indiciaire_brut += (
(categorie_salarie == TypesCategorieSalarie[categorie].index) * brut / (1 + 0.0076 * (1 - taux_indemnite_exo_cotisation))
) # Prise en compte de l'indemnité compensatrice de csg, qui sera recalculée. Prise en compte imparfaite des cotisations (PSS notamment)
else:
traitement_indiciaire_brut += (
(categorie_salarie == TypesCategorieSalarie[categorie].index) * brut
)
if (categorie_salarie == TypesCategorieSalarie[categorie].index).any():
log.debug("Pour {} : brut = {}".format(TypesCategorieSalarie[categorie].index, brut))
log.debug('bareme direct: {}'.format(bareme))
Expand All @@ -395,12 +408,18 @@ def create_traitement_indiciaire_brut(individus, period = None, revenu_type = 'i
individus['primes_fonction_publique'] = TAUX_DE_PRIME * traitement_indiciaire_brut


def create_revenus_remplacement_bruts(individus, period, tax_benefit_system):
def create_revenus_remplacement_bruts(individus, period, tax_benefit_system, revenu_type = 'imposable'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je mettrait plutôt le 'net' par défaut car c'est l'option qui existait avant. Cela permet aux utilisateurs de ne pas avoir de changements / erreurs dans leurs calculs s'ils utilisaient déjà cette fonction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La fonction a été rajoutée par Paul il y a deux semaines, je ne pense donc pas qu'elle soit encore consciemment utilisée. A priori, le mieux est de mettre le salaire net, mais comme le paramètre par défaut dans la fonction précédente (create_traitement_indiciaire_brut ) est imposable je me suis dit que c'était bien d'harmoniser. Cependant, le mieux est probablement de faire apparaître la "vraie" façon d'inverser (le seul défaut est qu'on ne peut plus faire tourner toutes les fonctions avec les mêmes paramètres par défaut, mais c'était déjà difficile à faire avant)

assert 'taux_csg_remplacement' in individus

individus.chomage_imposable.fillna(0, inplace = True)
individus.retraite_imposable.fillna(0, inplace = True)
individus.salaire_net.fillna(0, inplace = True)
if revenu_type == 'imposable':
assert 'salaire_imposable' in individus.columns
salaire_pour_inversion = individus.salaire_imposable
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

à la place du else je mettrai un elif revenu_type == 'net puis un else: raise error not implemented

assert 'salaire_net' in individus.columns
salaire_pour_inversion = individus.salaire_net
salaire_pour_inversion.fillna(0, inplace = True)

parameters = tax_benefit_system.get_parameters_at_instant(period.start)
csg = parameters.prelevements_sociaux.contributions_sociales.csg
Expand All @@ -410,13 +429,20 @@ def create_revenus_remplacement_bruts(individus, period, tax_benefit_system):
seuil_abattement_csg_chomage = parameters.prelevements_sociaux.contributions_sociales.csg.remplacement.allocations_chomage.deductible.abattement.thresholds[1]
taux_plein = csg_deductible_chomage.taux_plein
taux_reduit = csg_deductible_chomage.taux_reduit
liste_smic_mensuel = []
for month in period.get_subperiods(DateUnit.MONTH):
smic_horaire_mois = openfisca_france_tax_benefit_system.parameters.marche_travail.salaire_minimum.smic.smic_b_horaire(month)
nb_heures_mois = openfisca_france_tax_benefit_system.parameters.marche_travail.salaire_minimum.smic.nb_heures_travail_mensuel(month)
smic_mensuel = smic_horaire_mois * nb_heures_mois
liste_smic_mensuel.append(smic_mensuel)

seuil_chomage_net_exoneration = (
(35 * 52) * smic_horaire_brut[period.start.year]
sum(liste_smic_mensuel)
* (
(individus.taux_csg_remplacement == 2) / (1 - taux_reduit)
+ (individus.taux_csg_remplacement >= 3) / (1 - taux_plein)
)
) - individus.salaire_net
) - salaire_pour_inversion # théoriquement, il s'agit du net pour salaire et rpns, mais il n'est pas toujours disponible en pratique
exonere_csg_chomage = (
(individus.taux_csg_remplacement < 2)
| (individus.chomage_imposable <= seuil_chomage_net_exoneration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def create_individu_variables_brutes(
create_taux_csg_remplacement(individus, period, tax_benefit_system)
created_variables.append('taux_csg_remplacement')

create_revenus_remplacement_bruts(individus, period, tax_benefit_system)
create_revenus_remplacement_bruts(individus, period, tax_benefit_system, revenu_type = revenu_type)
created_variables.append('chomage_brut')
created_variables.append('retraite_brute')

Expand Down
2 changes: 1 addition & 1 deletion tests/test_inversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# Inverse incomes from net to gross : the tested functions

create_taux_csg_remplacement(individus, period(year), tax_benefit_system)
create_revenus_remplacement_bruts(individus, period(year), tax_benefit_system)
create_revenus_remplacement_bruts(individus, period(year), tax_benefit_system, revenu_type = 'net')

# Test against chomage_brut_test

Expand Down
Loading