Skip to content
Snippets Groups Projects
Commit a13c33a3 authored by sandcha's avatar sandcha
Browse files

Merge branch 'fix-bareme-calculus' into 'main'

Corrige l'identification des seuils et valeurs de barèmes amendés

See merge request !20
parents fd20b5fa 65485c02
Branches
Tags 4.2.1
1 merge request!20Corrige l'identification des seuils et valeurs de barèmes amendés
Pipeline #21455 passed
# CHANGELOG
### 4.2.1 [!20](https://git.leximpact.dev/leximpact/simulateur-dotations-communes/leximpact-dotations-back/-/merge_requests/20)
* Correction d'un crash. Optimisation.
* Détails :
* Corrige l'identification d'un barème amendé dans `extract_openfisca_parameter(...)`
* Regroupe les traitements de paramètres amendés dans `leximpact_dotations_back/mapping/reform_parameters.py`
* Optimise l'identification des paramètres `openfisca-france-dotations-locales`
* Recherche chaque paramètre dans un dictionnaire des `Parameter` déjà connus dans `get_openfisca_parameter()` avant de rechercher dans l'arbre des paramètres `openfisca-france-dotations-locales`
## 4.2.0 [!16](https://git.leximpact.dev/leximpact/simulateur-dotations-communes/leximpact-dotations-back/-/merge_requests/16)
* Correction d'un crash
* Correction d'un crash.
* Détails :
* Corrige la préparation des données pour 2024 :
- Supprime l'injection en input de la dotation forfaitaire notifiée (le calcul de la dotation forfaitaire ne se faisait pas)
......@@ -14,7 +23,7 @@
## 4.1.0 [!18](https://git.leximpact.dev/leximpact/simulateur-dotations-communes/leximpact-dotations-back/-/merge_requests/18)
* Correction de la simulation
* Correction de la simulation.
* Période : 2024 + PLF 2025
* Détails :
* Corrige `/calculate` en cas de PLF
......
......@@ -5,9 +5,9 @@ from openfisca_core import periods
from openfisca_core.parameters import Parameter
from openfisca_core.reforms import Reform
from leximpact_dotations_back.mapping.reform_parameters import get_openfisca_parameter
from leximpact_dotations_back.computing.dotations_simulation import DotationsSimulation
# configure _root_ logger
logger = logging.getLogger()
REFORM_YEAR = 2024
......@@ -28,35 +28,16 @@ class reform_from_amendement(Reform):
self.amendement_parameters: Dict[str, float] = amendement_parameters
super().__init__(baseline)
def get_openfisca_parameter(self, parameters, parameter_dot_name) -> Parameter | None:
try:
parameter_name = parameter_dot_name
cles = parameter_name.split('.')
for cle in cles:
parameters = parameters.children[cle]
logger.debug(f"La valeur correspondante à '{parameter_name}' est : {parameters}")
return parameters # un Parameter si parameter_dot_name indique bien une feuille de l'arbre des parameters
except KeyError:
# La clé '{cle}' n'a pas été trouvée dans modèle
logger.error(f"Paramètre '{parameter_name}' introuvable.")
return None
except AttributeError:
logger.error("La structure du modèle n'est pas un dictionnaire à un niveau donné.")
return None
def reform_parameters_from_amendement(self, parameters, amendement_parameters):
reform_period = periods.period(REFORM_YEAR)
for parameter_name, value in amendement_parameters.items():
try:
one_parameter: Parameter = self.get_openfisca_parameter(parameters, parameter_name)
one_parameter: Parameter = get_openfisca_parameter(parameters, parameter_name)
if one_parameter is not None:
one_parameter.update(period=reform_period, value=value)
except ValueError as e:
# TODO ajouter l'information à la réponse d'API web ?
logger.warning(f"[Amendement] Échec de la réforme du paramètre '{parameter_name}': {e}")
return parameters
......@@ -85,35 +66,17 @@ class reform_from_plf(Reform):
self.plf_parameters: Dict[str, float] = plf_parameters
super().__init__(baseline)
def get_openfisca_parameter(parameters, parameter_dot_name) -> Parameter | None:
try:
parameter_name = parameter_dot_name
cles = parameter_name.split('.')
for cle in cles:
parameters = parameters.children[cle]
logger.debug(f"La valeur correspondante à '{parameter_name}' est : {parameters}")
return parameters # un Parameter si parameter_dot_name indique bien une feuille de l'arbre des parameters
except KeyError:
# La clé '{cle}' n'a pas été trouvée dans modèle
logger.error(f"Paramètre '{parameter_name}' introuvable.")
return None
except AttributeError:
logger.error("La structure du modèle n'est pas un dictionnaire à un niveau donné.")
return None
def reform_parameters_from_plf(self, parameters, plf_parameters):
reform_period = periods.period(REFORM_YEAR)
for parameter_name, value in plf_parameters.items():
try:
one_parameter: Parameter = self.get_openfisca_parameter(parameters, parameter_name)
one_parameter: Parameter = get_openfisca_parameter(parameters, parameter_name)
if one_parameter is not None:
one_parameter.update(period=reform_period, value=value)
except ValueError as e:
# TODO ajouter l'information à la réponse d'API web ?
logger.warning(f"[PLF] Échec de la réforme du paramètre '{parameter_name}': {e}")
return parameters
......
editable_parameters_2024 = [
# DF
"dotation_forfaitaire.montant_minimum_par_habitant",
"dotation_forfaitaire.montant_maximum_par_habitant",
"dotation_forfaitaire.ecretement.seuil_rapport_potentiel_fiscal",
"dotation_forfaitaire.ecretement.plafond_pourcentage_recettes_max",
# DSR
"dotation_solidarite_rurale.seuil_nombre_habitants",
"dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_nombre_habitants_chef_lieu",
"dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_part_population_canton",
......@@ -10,7 +17,6 @@ editable_parameters_2024 = [
"dotation_solidarite_rurale.bourg_centre.attribution.plafond_population",
"dotation_solidarite_rurale.bourg_centre.attribution.plafond_effort_fiscal",
"dotation_solidarite_rurale.bourg_centre.attribution.coefficient_zrr",
"population.plafond_dgf",
"dotation_solidarite_rurale.perequation.seuil_rapport_potentiel_financier",
"dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_habitant",
"dotation_solidarite_rurale.attribution.poids_longueur_voirie",
......@@ -18,6 +24,15 @@ editable_parameters_2024 = [
"dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_hectare",
"dotation_solidarite_rurale.attribution.plafond_effort_fiscal",
"dotation_solidarite_rurale.cible.eligibilite.seuil_classement",
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_potentiel_financier",
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_revenu",
"dotation_solidarite_rurale.perequation.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.perequation.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.bourg_centre.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.bourg_centre.attribution.plafond_ratio_progression",
"dotation_solidarite_rurale.augmentation_montant",
# DSU
"dotation_solidarite_urbaine.eligibilite.seuil_bas_nombre_habitants",
"dotation_solidarite_urbaine.eligibilite.seuil_haut_nombre_habitants",
"dotation_solidarite_urbaine.eligibilite.seuil_rapport_potentiel_financier",
......@@ -33,16 +48,8 @@ editable_parameters_2024 = [
"dotation_solidarite_urbaine.attribution.poids_quartiers_prioritaires_ville",
"dotation_solidarite_urbaine.attribution.poids_zone_franche_urbaine",
"dotation_solidarite_urbaine.attribution.augmentation_max",
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_potentiel_financier",
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_revenu",
"dotation_solidarite_rurale.perequation.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.perequation.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.bourg_centre.attribution.plancher_ratio_progression",
"dotation_solidarite_rurale.bourg_centre.attribution.plafond_ratio_progression",
"dotation_solidarite_urbaine.augmentation_montant",
"dotation_solidarite_rurale.augmentation_montant",
"dotation_forfaitaire.montant_minimum_par_habitant",
"dotation_forfaitaire.montant_maximum_par_habitant",
"dotation_forfaitaire.ecretement.seuil_rapport_potentiel_fiscal",
"dotation_forfaitaire.ecretement.plafond_pourcentage_recettes_max",
# Critères généraux
"population.plafond_dgf",
]
import logging
from openfisca_core.parameters import Parameter, ParameterNode
# configure _root_ logger
logger = logging.getLogger()
def extract_openfisca_parameter(parameters: ParameterNode, parameter_dot_name: str) -> Parameter | None:
'''
Extrait le Parameter openfisca de l'arbre des paramètres fourni
à partir de son nom en notation pointée.
'''
parameter_name: str = parameter_dot_name
is_scale_bracket: bool = False
bracket_openfisca_index: int | None = None
remaining_path: str | None = None
if 'brackets' in parameter_dot_name:
is_scale_bracket = True
start_bracket_index = parameter_dot_name.index('.brackets[') + 10 # 10 pour longueur '.brackets['
parameter_name = parameter_dot_name[:start_bracket_index - 10] # tout ce qui précède '.brackets['
end_bracket_index = parameter_dot_name.index(']')
bracket_openfisca_index = int(parameter_dot_name[start_bracket_index:end_bracket_index])
# le remaining_path attendu est 'threshold', 'rate' ou 'amount'
remaining_path = parameter_dot_name[end_bracket_index + 2:] # 2 pour longueur "]."
try:
# pour un paramètre simple, parameter_name est le nom fourni en argument
# pour un barème, parameter_name est le nom fourni en argument tronqué à partir de '.brackets'
cles = parameter_name.split('.')
for cle in cles:
parameters = parameters.children[cle]
if is_scale_bracket:
# parameters.brackets est list[ParameterScaleBracket]
# mais structure étrange : parameters.brackets[bracket_openfisca_index] est un ParameterNode
parameters = parameters.brackets[bracket_openfisca_index].children[remaining_path]
logger.debug(f"La valeur correspondante à '{parameter_name}' est : {parameters}")
return parameters # un Parameter si parameter_dot_name indique bien une feuille de l'arbre des parameters
except KeyError:
# La clé '{cle}' n'a pas été trouvée dans modèle
logger.error(f"Paramètre '{parameter_name}' introuvable.")
return None
except AttributeError:
logger.error(f"La structure de l'arbre de paramètres n'est pas un dictionnaire à partir de : {parameters}")
return None
def get_openfisca_parameter(openfisca_parameters: ParameterNode, openfisca_parameter_suffix: str) -> Parameter:
identified_reform_parameters_2024 = {
# DF
# "dotation_forfaitaire.montant_minimum_par_habitant",
# "dotation_forfaitaire.montant_maximum_par_habitant",
"dotation_forfaitaire.ecretement.plafond_pourcentage_recettes_max": openfisca_parameters.dotation_forfaitaire.ecretement.plafond_pourcentage_recettes_max,
"dotation_forfaitaire.ecretement.seuil_rapport_potentiel_fiscal": openfisca_parameters.dotation_forfaitaire.ecretement.seuil_rapport_potentiel_fiscal,
# DSR
"dotation_solidarite_rurale.attribution.plafond_effort_fiscal": openfisca_parameters.dotation_solidarite_rurale.attribution.plafond_effort_fiscal,
"dotation_solidarite_rurale.attribution.poids_enfants": openfisca_parameters.dotation_solidarite_rurale.attribution.poids_enfants,
"dotation_solidarite_rurale.attribution.poids_longueur_voirie": openfisca_parameters.dotation_solidarite_rurale.attribution.poids_longueur_voirie,
"dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_habitant": openfisca_parameters.dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_habitant,
"dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_hectare": openfisca_parameters.dotation_solidarite_rurale.attribution.poids_potentiel_financier_par_hectare,
"dotation_solidarite_rurale.augmentation_montant": openfisca_parameters.dotation_solidarite_rurale.augmentation_montant,
"dotation_solidarite_rurale.bourg_centre.attribution.coefficient_zrr": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.attribution.coefficient_zrr,
"dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_part_population_dgf_agglomeration_departement": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_part_population_dgf_agglomeration_departement,
"dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_agglomeration": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_agglomeration,
"dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_chef_lieu_de_canton": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_chef_lieu_de_canton,
"dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_maximum_commune_agglomeration": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.exclusion.seuil_population_dgf_maximum_commune_agglomeration,
"dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_nombre_habitants_chef_lieu": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_nombre_habitants_chef_lieu,
"dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_part_population_canton": openfisca_parameters.dotation_solidarite_rurale.bourg_centre.eligibilite.seuil_part_population_canton,
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_potentiel_financier": openfisca_parameters.dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_potentiel_financier,
"dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_revenu": openfisca_parameters.dotation_solidarite_rurale.cible.eligibilite.indice_synthetique.poids_revenu,
"dotation_solidarite_rurale.cible.eligibilite.seuil_classement": openfisca_parameters.dotation_solidarite_rurale.cible.eligibilite.seuil_classement,
"dotation_solidarite_rurale.perequation.seuil_rapport_potentiel_financier": openfisca_parameters.dotation_solidarite_rurale.perequation.seuil_rapport_potentiel_financier,
"dotation_solidarite_rurale.seuil_nombre_habitants": openfisca_parameters.dotation_solidarite_rurale.seuil_nombre_habitants,
# DSU
"dotation_solidarite_urbaine.attribution.facteur_classement_max": openfisca_parameters.dotation_solidarite_urbaine.attribution.facteur_classement_max,
"dotation_solidarite_urbaine.attribution.facteur_classement_min": openfisca_parameters.dotation_solidarite_urbaine.attribution.facteur_classement_min,
"dotation_solidarite_urbaine.attribution.plafond_effort_fiscal": openfisca_parameters.dotation_solidarite_urbaine.attribution.plafond_effort_fiscal,
"dotation_solidarite_urbaine.attribution.poids_quartiers_prioritaires_ville": openfisca_parameters.dotation_solidarite_urbaine.attribution.poids_quartiers_prioritaires_ville,
"dotation_solidarite_urbaine.attribution.poids_zone_franche_urbaine": openfisca_parameters.dotation_solidarite_urbaine.attribution.poids_zone_franche_urbaine,
"dotation_solidarite_urbaine.augmentation_montant": openfisca_parameters.dotation_solidarite_urbaine.augmentation_montant,
"dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_aides_au_logement": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_aides_au_logement,
"dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_logements_sociaux": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_logements_sociaux,
"dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_potentiel_financier": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_potentiel_financier,
"dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_revenu": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.indice_synthetique.poids_revenu,
"dotation_solidarite_urbaine.eligibilite.part_eligible_seuil_bas": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.part_eligible_seuil_bas,
"dotation_solidarite_urbaine.eligibilite.part_eligible_seuil_haut": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.part_eligible_seuil_haut,
"dotation_solidarite_urbaine.eligibilite.seuil_bas_nombre_habitants": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.seuil_bas_nombre_habitants,
"dotation_solidarite_urbaine.eligibilite.seuil_haut_nombre_habitants": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.seuil_haut_nombre_habitants,
"dotation_solidarite_urbaine.eligibilite.seuil_rapport_potentiel_financier": openfisca_parameters.dotation_solidarite_urbaine.eligibilite.seuil_rapport_potentiel_financier,
# Critères généraux
"population.plafond_dgf.brackets[0].amount": openfisca_parameters.population.plafond_dgf.brackets[0].amount,
"population.plafond_dgf.brackets[1].amount": openfisca_parameters.population.plafond_dgf.brackets[1].amount,
"population.plafond_dgf.brackets[2].amount": openfisca_parameters.population.plafond_dgf.brackets[2].amount,
"population.plafond_dgf.brackets[1].threshold": openfisca_parameters.population.plafond_dgf.brackets[1].threshold,
"population.plafond_dgf.brackets[3].threshold": openfisca_parameters.population.plafond_dgf.brackets[3].threshold
}
parameter: Parameter | None = identified_reform_parameters_2024.get(openfisca_parameter_suffix)
if parameter is None:
parameter = extract_openfisca_parameter(
openfisca_parameters,
openfisca_parameter_suffix
)
return parameter
[tool.poetry]
name = "leximpact-dotations-back"
version = "4.2.0"
version = "4.2.1"
description = "Simulating annual State grants to the French administrative divisions"
authors = ["LexImpact <leximpact@assemblee-nationale.fr>"]
license = "AGPL-3.0-or-later"
......
import logging
import pytest
from openfisca_core.reforms import Reform
from openfisca_france_dotations_locales import CountryTaxBenefitSystem as OpenFiscaFranceDotationsLocales
from leximpact_dotations_back.computing.reform import reform_from_amendement
@pytest.fixture
def model():
return OpenFiscaFranceDotationsLocales()
@pytest.fixture
def caplog(caplog):
caplog.set_level(logging.WARNING)
return caplog
def test_reform_from_amendement_parameter_update(model):
empty_amendement = {}
empty_amendement_model = reform_from_amendement(model, empty_amendement)
assert empty_amendement_model.__class__.__bases__[0] is Reform
assert empty_amendement_model.parameters.dotation_solidarite_rurale.seuil_nombre_habitants(2024) == 10_000
dsr_seuil_amendement = {
"dotation_solidarite_rurale.seuil_nombre_habitants": 5_000
}
dsr_seuil_amendement_model = reform_from_amendement(model, dsr_seuil_amendement)
assert dsr_seuil_amendement_model.parameters.dotation_solidarite_rurale.seuil_nombre_habitants(2024) == 5_000
def test_reform_from_amendement_parameter_name_error(model, caplog):
surprise_amendement = {
"dotation_truc.parametre.mal.nomme": 42
}
surprise_model = reform_from_amendement(model, surprise_amendement)
assert "Paramètre 'dotation_truc.parametre.mal.nomme' introuvable." in caplog.text # via extract_openfisca_parameter(...)
assert surprise_model is not None # devrait avoir le même contenu que 'model'
import pytest
from openfisca_core.parameters import Parameter
from openfisca_france_dotations_locales import CountryTaxBenefitSystem as OpenFiscaFranceDotationsLocales
from leximpact_dotations_back.mapping.reform_parameters import extract_openfisca_parameter, get_openfisca_parameter
@pytest.fixture
def model():
return OpenFiscaFranceDotationsLocales()
def test_extract_openfisca_parameter(model):
simple_parameter_suffix = "dotation_solidarite_rurale.attribution.poids_longueur_voirie"
simple_parameter: Parameter | None = extract_openfisca_parameter(model.parameters, simple_parameter_suffix)
assert simple_parameter is not None
assert simple_parameter(2024) == 0.30
scale_parameter_suffix = "population.plafond_dgf.brackets[0].threshold"
scale_item_parameter: Parameter | None = extract_openfisca_parameter(model.parameters, scale_parameter_suffix)
print(scale_item_parameter)
assert scale_item_parameter is not None
assert scale_item_parameter(2024) == 0
def test_get_openfisca_parameter(model):
known_parameter_suffix = "dotation_solidarite_rurale.augmentation_montant" # in the pre-identified list of parameters
known_parameter: Parameter | None = get_openfisca_parameter(model.parameters, known_parameter_suffix)
assert known_parameter is not None
assert known_parameter(2024) == 150_000_000
unknown_parameter_suffix = "population.plafond_dgf.brackets[0].threshold" # not in the 'identified_reform_parameters_2024' list of parameters
extracted_parameter: Parameter | None = get_openfisca_parameter(model.parameters, unknown_parameter_suffix)
assert extracted_parameter is not None
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment