From 84498a40af175fb314ff561c9f61095212574073 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Fri, 19 Jan 2024 15:20:11 +0100 Subject: [PATCH 01/12] Cleaning --- Makefile | 49 +++++-- openfisca_tunisia_pension/__init__.py | 4 +- openfisca_tunisia_pension/entities.py | 33 ++--- openfisca_tunisia_pension/model/base.py | 3 - openfisca_tunisia_pension/model/data.py | 3 - openfisca_tunisia_pension/scenarios.py | 137 +++++++++--------- .../scripts/migrations/xml_to_yaml_tunisia.py | 32 ---- openfisca_tunisia_pension/tests/base.py | 3 - .../tests/test_pension.py | 2 - openfisca_tunisia_pension/tests/test_yaml.py | 40 ----- .../tunisia_pension_taxbenefitsystem.py | 2 - setup.cfg | 28 ++-- setup.py | 23 +-- 13 files changed, 152 insertions(+), 207 deletions(-) delete mode 100644 openfisca_tunisia_pension/scripts/migrations/xml_to_yaml_tunisia.py delete mode 100755 openfisca_tunisia_pension/tests/test_yaml.py diff --git a/Makefile b/Makefile index 5049e48..2ebe30a 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,53 @@ all: test -check-no-prints: - @test -z "`git grep -w print openfisca_tunisia_pension/model`" - -check-syntax-errors: - python -m compileall -q . +uninstall: + pip freeze | grep -v "^-e" | xargs pip uninstall -y clean: rm -rf build dist - find . -name '*.mo' -exec rm \{\} \; find . -name '*.pyc' -exec rm \{\} \; -flake8: +deps: + pip install --upgrade pip build twine + +install: deps + @# Install OpenFisca-Tunisia-Pension for development. + @# `make install` installs the editable version of OpenFisca-Tunisia. + @# This allows contributors to test as they code. + pip install --editable .[dev] --upgrade + pip install openfisca-core[web-api] + +build: clean deps + @# Install OpenFisca-Tunisia-Pension for deployment and publishing. + @# `make build` allows us to be be sure tests are run against the packaged version + @# of OpenFisca-Tunisia-Pension, the same we put in the hands of users and reusers. + python -m build + pip uninstall --yes openfisca-tunisia-pension + find dist -name "*.whl" -exec pip install {}[dev] \; + pip install openfisca-core[web-api] + +check-syntax-errors: + python -m compileall -q . + +format-style: + @# Do not analyse .gitignored files. + @# `make` needs `$$` to output `$`. Ref: http://stackoverflow.com/questions/2382764. + autopep8 `git ls-files | grep "\.py$$"` + +check-style: @# Do not analyse .gitignored files. @# `make` needs `$$` to output `$`. Ref: http://stackoverflow.com/questions/2382764. flake8 `git ls-files | grep "\.py$$"` -test: check-syntax-errors check-no-prints +check-yaml: + @# check yaml style + .github/lint-changed-yaml-tests.sh + +check-all-yaml: + @# check yaml style + yamllint . + +test: clean check-syntax-errors check-style @# Launch tests from openfisca_tunisia_pension/tests directory (and not .) because TaxBenefitSystem must be initialized @# before parsing source files containing formulas. - nosetests openfisca_tunisia_pension/tests --exe --with-doctest + openfisca test --country-package openfisca_tunisia_pension tests diff --git a/openfisca_tunisia_pension/__init__.py b/openfisca_tunisia_pension/__init__.py index b5673c4..6b04e0a 100644 --- a/openfisca_tunisia_pension/__init__.py +++ b/openfisca_tunisia_pension/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - -from tunisia_pension_taxbenefitsystem import TunisiaPensionTaxBenefitSystem +from openfisca_tunisia_pension.tunisia_pension_taxbenefitsystem import TunisiaPensionTaxBenefitSystem CountryTaxBenefitSystem = TunisiaPensionTaxBenefitSystem diff --git a/openfisca_tunisia_pension/entities.py b/openfisca_tunisia_pension/entities.py index 7bac2fc..aceb7a2 100644 --- a/openfisca_tunisia_pension/entities.py +++ b/openfisca_tunisia_pension/entities.py @@ -1,60 +1,57 @@ -# -*- coding: utf-8 -*- - - from openfisca_core.entities import build_entity Individu = build_entity( - key = "individu", - plural = "individus", - label = u'Individu', + key = 'individu', + plural = 'individus', + label = 'Individ', is_person = True ) FoyerFiscal = build_entity( - key = "foyer_fiscal", - plural = "foyers_fiscaux", - label = u'Déclaration d’impôts', + key = 'foyer_fiscal', + plural = 'foyers_fiscaux', + label = 'Déclaration d’impôts', roles = [ { 'key': 'declarant', 'plural': 'declarants', - 'label': u'Déclarants', + 'label': 'Déclarants', 'subroles': ['declarant_principal', 'conjoint'] }, { 'key': 'personne_a_charge', 'plural': 'personnes_a_charge', - 'label': u'Personnes à charge' + 'label': 'Personnes à charge' }, ] ) Menage = build_entity( - key = "menage", - plural = "menages", - label = u'Logement principal', + key = 'menage', + plural = 'menages', + label = 'Logement principal', roles = [ { 'key': 'personne_de_reference', - 'label': u'Personne de référence', + 'label': 'Personne de référence', 'max': 1 }, { 'key': 'conjoint', - 'label': u'Conjoint', + 'label': 'Conjoint', 'max': 1 }, { 'key': 'enfant', 'plural': 'enfants', - 'label': u'Enfants', + 'label': 'Enfants', 'max': 2 }, { 'key': 'autre', 'plural': 'autres', - 'label': u'Autres' + 'label': 'Autres' } ] ) diff --git a/openfisca_tunisia_pension/model/base.py b/openfisca_tunisia_pension/model/base.py index 0f51f31..a5d5a9d 100644 --- a/openfisca_tunisia_pension/model/base.py +++ b/openfisca_tunisia_pension/model/base.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from datetime import date from openfisca_core.model_api import * from openfisca_tunisia_pension.entities import FoyerFiscal, Individu, Menage diff --git a/openfisca_tunisia_pension/model/data.py b/openfisca_tunisia_pension/model/data.py index a6a2b68..1894133 100644 --- a/openfisca_tunisia_pension/model/data.py +++ b/openfisca_tunisia_pension/model/data.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from openfisca_tunisia_pension.model.base import * diff --git a/openfisca_tunisia_pension/scenarios.py b/openfisca_tunisia_pension/scenarios.py index 917df7c..5f9d91e 100644 --- a/openfisca_tunisia_pension/scenarios.py +++ b/openfisca_tunisia_pension/scenarios.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import collections import datetime import itertools @@ -9,7 +6,7 @@ import uuid from openfisca_core import conv, scenarios -from entities import Individu, FoyerFiscal, Menage +from openfisca_tunisia_pension.entities import Individu, FoyerFiscal, Menage def N_(message): @@ -17,7 +14,7 @@ def N_(message): log = logging.getLogger(__name__) -year_or_month_or_day_re = re.compile(ur'(18|19|20)\d{2}(-(0[1-9]|1[0-2])(-([0-2]\d|3[0-1]))?)?$') +# year_or_month_or_day_re = re.compile(ur'(18|19|20)\d{2}(-(0[1-9]|1[0-2])(-([0-2]\d|3[0-1]))?)?$') class Scenario(scenarios.AbstractScenario): @@ -89,7 +86,7 @@ def post_process_test_case(self, test_case, period, state): # conv.empty_to_none, # conv.not_none, # conv.test(lambda parents: len(parents) <= 2, - # error = N_(u'A "famille" must have at most 2 "parents"')) + # error = N_('A "famille" must have at most 2 "parents"')) # ), # ), # default = conv.noop, @@ -107,7 +104,7 @@ def post_process_test_case(self, test_case, period, state): conv.not_none, conv.test( lambda declarants: len(declarants) <= 2, - error = N_(u'A "foyer_fiscal" must have at most 2 "declarants"'), + error = N_('A "foyer_fiscal" must have at most 2 "declarants"'), ), conv.uniform_sequence(conv.pipe( )), @@ -184,7 +181,7 @@ def post_process_test_case(self, test_case, period, state): ), conv.default([]), ), - ).iteritems(), + ).items(), )), drop_none_values = True, ), @@ -230,7 +227,7 @@ def post_process_test_case(self, test_case, period, state): conv.not_none, ), personne_de_reference = conv.test_isinstance((basestring, int)), - ).iteritems(), + ).items(), )), drop_none_values = True, ), @@ -289,40 +286,40 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i for individu_id in individus_without_foyer_fiscal[:]: # Tente d'affecter l'individu à un foyer fiscal d'après son ménage. menage, menage_role = find_menage_and_role(test_case, individu_id) - if menage_role == u'personne_de_reference': - conjoint_id = menage[u'conjoint'] + if menage_role == 'personne_de_reference': + conjoint_id = menage['conjoint'] if conjoint_id is not None: foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, conjoint_id) - if other_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 1: - # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est personne de - # référence dans un ménage, qu'il y a un conjoint dans ce ménage et que ce + if other_role == 'declarants' and len(foyer_fiscal['declarants']) == 1: + # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est personne de + # référence dans un ménage, q'il y a un conjoint dans ce ménage et que ce # conjoint est seul déclarant dans un foyer fiscal, alors ajoute l'individu comme # autre déclarant de ce foyer fiscal. - foyer_fiscal[u'declarants'].append(individu_id) + foyer_fiscal['declarants'].append(individu_id) individus_without_foyer_fiscal.remove(individu_id) - elif menage_role == u'conjoint': - personne_de_reference_id = menage[u'personne_de_reference'] + elif menage_role == 'conjoint': + personne_de_reference_id = menage['personne_de_reference'] if personne_de_reference_id is not None: foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, personne_de_reference_id) - if other_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 1: - # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est conjoint - # dans un ménage, qu'il y a une personne de référence dans ce ménage et que + if other_role == 'declarants' and len(foyer_fiscal['declarants']) == 1: + # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est conjoint + # dans un ménage, q'il y a une personne de référence dans ce ménage et que # cette personne est seul déclarant dans un foyer fiscal, alors ajoute l'individu # comme autre déclarant de ce foyer fiscal. - foyer_fiscal[u'declarants'].append(individu_id) + foyer_fiscal['declarants'].append(individu_id) individus_without_foyer_fiscal.remove(individu_id) - elif menage_role == u'enfants' and ( - menage['personne_de_reference'] is not None or menage[u'conjoint'] is not None): - for other_id in (menage['personne_de_reference'], menage[u'conjoint']): + elif menage_role == 'enfants' and ( + menage['personne_de_reference'] is not None or menage['conjoint'] is not None): + for other_id in (menage['personne_de_reference'], menage['conjoint']): if other_id is None: continue foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, other_id) - if other_role == u'declarants': - # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est enfant dans - # un ménage, qu'il y a une personne à charge ou un conjoint dans ce ménage et que + if other_role == 'declarants': + # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est enfant dans + # un ménage, q'il y a une personne à charge ou un conjoint dans ce ménage et que # celui-ci est déclarant dans un foyer fiscal, alors ajoute l'individu comme # personne à charge de ce foyer fiscal. - foyer_fiscal[u'personnes_a_charge'].append(individu_id) + foyer_fiscal['personnes_a_charge'].append(individu_id) individus_without_foyer_fiscal.remove(individu_id) break @@ -330,13 +327,13 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i # L'individu n'est toujours pas affecté à un foyer fiscal. individu = individu_by_id[individu_id] age = find_age(individu, period.start.date) - if len(new_foyer_fiscal[u'declarants']) < 2 and (age is None or age >= 18): - new_foyer_fiscal[u'declarants'].append(individu_id) + if len(new_foyer_fiscal['declarants']) < 2 and (age is None or age >= 18): + new_foyer_fiscal['declarants'].append(individu_id) else: - new_foyer_fiscal[u'personnes_a_charge'].append(individu_id) + new_foyer_fiscal['personnes_a_charge'].append(individu_id) if new_foyer_fiscal_id is None: - new_foyer_fiscal[u'id'] = new_foyer_fiscal_id = unicode(uuid.uuid4()) - test_case[u'foyers_fiscaux'].append(new_foyer_fiscal) + new_foyer_fiscal['id'] = new_foyer_fiscal_id = unicode(uuid.uuid4()) + test_case['foyers_fiscaux'].append(new_foyer_fiscal) individus_without_foyer_fiscal.remove(individu_id) # Affecte à un ménage chaque individu qui n'appartient à aucun d'entre eux. @@ -350,68 +347,68 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i for individu_id in menages_individus_id[:]: # Tente d'affecter l'individu à un ménage d'après son foyer fiscal. foyer_fiscal, foyer_fiscal_role = find_foyer_fiscal_and_role(test_case, individu_id) - if foyer_fiscal_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 2: - for declarant_id in foyer_fiscal[u'declarants']: + if foyer_fiscal_role == 'declarants' and len(foyer_fiscal['declarants']) == 2: + for declarant_id in foyer_fiscal['declarants']: if declarant_id != individu_id: menage, other_role = find_menage_and_role(test_case, declarant_id) - if other_role == u'personne_de_reference' and menage[u'conjoint'] is None: - # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant - # dans un foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et que - # cet autre déclarant est personne de référence dans un ménage et qu'il n'y a + if other_role == 'personne_de_reference' and menage['conjoint'] is None: + # Quand l'individu n'est pas encore dans un ménage, mais q'il est déclarant + # dans un foyer fiscal, q'il y a un autre déclarant dans ce foyer fiscal et que + # cet autre déclarant est personne de référence dans un ménage et q'il n'y a # pas de conjoint dans ce ménage, alors ajoute l'individu comme conjoint de ce # ménage. - menage[u'conjoint'] = individu_id + menage['conjoint'] = individu_id menages_individus_id.remove(individu_id) - elif other_role == u'conjoint' and menage[u'personne_de_reference'] is None: - # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant - # dans une foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et - # que cet autre déclarant est conjoint dans un ménage et qu'il n'y a pas de + elif other_role == 'conjoint' and menage['personne_de_reference'] is None: + # Quand l'individu n'est pas encore dans un ménage, mais q'il est déclarant + # dans une foyer fiscal, q'il y a un autre déclarant dans ce foyer fiscal et + # que cet autre déclarant est conjoint dans un ménage et q'il n'y a pas de # personne de référence dans ce ménage, alors ajoute l'individu comme personne # de référence de ce ménage. - menage[u'personne_de_reference'] = individu_id + menage['personne_de_reference'] = individu_id menages_individus_id.remove(individu_id) break - elif foyer_fiscal_role == u'personnes_a_charge' and foyer_fiscal[u'declarants']: - for declarant_id in foyer_fiscal[u'declarants']: + elif foyer_fiscal_role == 'personnes_a_charge' and foyer_fiscal['declarants']: + for declarant_id in foyer_fiscal['declarants']: menage, other_role = find_menage_and_role(test_case, declarant_id) - if other_role in (u'personne_de_reference', u'conjoint'): - # Quand l'individu n'est pas encore dans un ménage, mais qu'il est personne à charge - # dans un foyer fiscal, qu'il y a un déclarant dans ce foyer fiscal et que ce + if other_role in ('personne_de_reference', 'conjoint'): + # Quand l'individu n'est pas encore dans un ménage, mais q'il est personne à charge + # dans un foyer fiscal, q'il y a un déclarant dans ce foyer fiscal et que ce # déclarant est personne de référence ou conjoint dans un ménage, alors ajoute # l'individu comme enfant de ce ménage. - menage[u'enfants'].append(individu_id) + menage['enfants'].append(individu_id) menages_individus_id.remove(individu_id) break if individu_id in menages_individus_id: # L'individu n'est toujours pas affecté à un ménage. - if new_menage[u'personne_de_reference'] is None: - new_menage[u'personne_de_reference'] = individu_id - elif new_menage[u'conjoint'] is None: - new_menage[u'conjoint'] = individu_id + if new_menage['personne_de_reference'] is None: + new_menage['personne_de_reference'] = individu_id + elif new_menage['conjoint'] is None: + new_menage['conjoint'] = individu_id else: - new_menage[u'enfants'].append(individu_id) + new_menage['enfants'].append(individu_id) if new_menage_id is None: - new_menage[u'id'] = new_menage_id = unicode(uuid.uuid4()) - test_case[u'menages'].append(new_menage) + new_menage['id'] = new_menage_id = unicode(uuid.uuid4()) + test_case['menages'].append(new_menage) menages_individus_id.remove(individu_id) remaining_individus_id = set(individus_without_foyer_fiscal).union(menages_individus_id) if remaining_individus_id: individu_index_by_id = { - individu[u'id']: individu_index - for individu_index, individu in enumerate(test_case[u'individus']) + individu['id']: individu_index + for individu_index, individu in enumerate(test_case['individus']) } if error is None: error = {} for individu_id in remaining_individus_id: error.setdefault('individus', {})[individu_index_by_id[individu_id]] = state._( u"Individual is missing from {}").format( - state._(u' & ').join( + state._(' & ').join( word for word in [ - u'foyers_fiscaux' if individu_id in individus_without_foyer_fiscal else None, - u'menages' if individu_id in menages_individus_id else None, + 'foyers_fiscaux' if individu_id in individus_without_foyer_fiscal else None, + 'menages' if individu_id in menages_individus_id else None, ] if word is not None )) @@ -431,7 +428,7 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i conv.not_none, conv.test( lambda declarants: len(declarants) <= 2, - error = N_(u'A "foyer_fiscal" must have at most 2 "declarants"'), + error = N_('A "foyer_fiscal" must have at most 2 "declarants"'), ), # conv.uniform_sequence(conv.pipe( # conv.test(lambda individu_id: @@ -547,7 +544,7 @@ def to_json(self): personnes_a_charge = foyer_fiscal.get('personnes_a_charge') if personnes_a_charge: foyer_fiscal_json['personnes_a_charge'] = personnes_a_charge - for column_name, variable_value in foyer_fiscal.iteritems(): + for column_name, variable_value in foyer_fiscal.items(): column = column_by_name.get(column_name) if column is not None and column.entity == FoyerFiscal: variable_value_json = column.transform_value_to_json(variable_value) @@ -560,7 +557,7 @@ def to_json(self): individus_json = [] for individu in (test_case.get('individus') or []): individu_json = collections.OrderedDict() - for column_name, variable_value in individu.iteritems(): + for column_name, variable_value in individu.items(): column = column_by_name.get(column_name) if column is not None and column.entity == Individu: variable_value_json = column.transform_value_to_json(variable_value) @@ -586,7 +583,7 @@ def to_json(self): autres = menage.get('autres') if autres: menage_json['autres'] = autres - for column_name, variable_value in menage.iteritems(): + for column_name, variable_value in menage.items(): column = column_by_name.get(column_name) if column is not None and column.entity == Menage: variable_value_json = column.transform_value_to_json(variable_value) @@ -605,7 +602,7 @@ def to_json(self): def find_foyer_fiscal_and_role(test_case, individu_id): for foyer_fiscal in test_case['foyers_fiscaux']: - for role in (u'declarants', u'personnes_a_charge'): + for role in ('declarants', 'personnes_a_charge'): if individu_id in foyer_fiscal[role]: return foyer_fiscal, role return None, None @@ -613,10 +610,10 @@ def find_foyer_fiscal_and_role(test_case, individu_id): def find_menage_and_role(test_case, individu_id): for menage in test_case['menages']: - for role in (u'personne_de_reference', u'conjoint'): + for role in ('personne_de_reference', 'conjoint'): if menage[role] == individu_id: return menage, role - for role in (u'enfants', u'autres'): + for role in ('enfants', 'autres'): if individu_id in menage[role]: return menage, role return None, None diff --git a/openfisca_tunisia_pension/scripts/migrations/xml_to_yaml_tunisia.py b/openfisca_tunisia_pension/scripts/migrations/xml_to_yaml_tunisia.py deleted file mode 100644 index e715865..0000000 --- a/openfisca_tunisia_pension/scripts/migrations/xml_to_yaml_tunisia.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- - -''' xml_to_yaml_tunisia.py : Parse XML parameter files for Openfisca-Tunisia and convert them to YAML files. Comments are NOT transformed. - -Usage : - `python xml_to_yaml_tunisia.py output_dir` -or just (output is written in a directory called `yaml_parameters`): - `python xml_to_yaml_tunisia.py` -''' - -import sys -import os - -from openfisca_tunisia_pension.tunisia_pension_taxbenefitsystem import COUNTRY_DIR -from openfisca_core.scripts.migrations.v16_2_to_v17 import xml_to_yaml - - -if len(sys.argv) > 1: - target_path = sys.argv[1] -else: - target_path = os.path.join(COUNTRY_DIR, 'parameters') - -param_dir = os.path.join(COUNTRY_DIR, 'param') -param_files = [ - 'param.xml', - ] -legislation_xml_info_list = [ - (os.path.join(param_dir, param_file), []) - for param_file in param_files -] - -xml_to_yaml.write_parameters(legislation_xml_info_list, target_path) diff --git a/openfisca_tunisia_pension/tests/base.py b/openfisca_tunisia_pension/tests/base.py index ccdb586..e2a57e3 100644 --- a/openfisca_tunisia_pension/tests/base.py +++ b/openfisca_tunisia_pension/tests/base.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from openfisca_core.tools import assert_near from openfisca_tunisia_pension import TunisiaPensionTaxBenefitSystem diff --git a/openfisca_tunisia_pension/tests/test_pension.py b/openfisca_tunisia_pension/tests/test_pension.py index 91a53c3..9b99c7b 100644 --- a/openfisca_tunisia_pension/tests/test_pension.py +++ b/openfisca_tunisia_pension/tests/test_pension.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from openfisca_core import periods from openfisca_core.tools import assert_near from openfisca_tunisia_pension.tests import base diff --git a/openfisca_tunisia_pension/tests/test_yaml.py b/openfisca_tunisia_pension/tests/test_yaml.py deleted file mode 100755 index 8489b7d..0000000 --- a/openfisca_tunisia_pension/tests/test_yaml.py +++ /dev/null @@ -1,40 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -import os -from nose.tools import nottest - -from openfisca_core.tools.test_runner import generate_tests - -from openfisca_tunisia_pension.tests import base - -nottest(generate_tests) - -options_by_dir = { - 'formulas': {}, - } - - -def test(): - for directory, options in options_by_dir.iteritems(): - path = os.path.abspath(os.path.join(os.path.dirname(__file__), directory)) - - if options.get('requires'): - # Check if the required package was successfully imported in tests/base.py - if getattr(base, options.get('requires')) is None: - continue - - if not options.get('default_relative_error_margin') and not options.get('default_absolute_error_margin'): - options['default_absolute_error_margin'] = 0.005 - - reform_keys = options.get('reforms') - tax_benefit_system = base.get_cached_composed_reform( - reform_keys = reform_keys, - tax_benefit_system = base.tax_benefit_system, - ) if reform_keys is not None else base.tax_benefit_system - - test_generator = generate_tests(tax_benefit_system, [path], options) - - for test in test_generator: - yield test - diff --git a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py index f8720c8..eb4bd54 100644 --- a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py +++ b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import glob import os diff --git a/setup.cfg b/setup.cfg index 9a7e54a..ffef11b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,22 @@ -# Flake8 +; E128/133: We prefer hang-closing visual indents +; E251: We prefer `function(x = 1)` over `function(x=1)` +; E501: We do not enforce a maximum line length +; F403/405: We ignore * imports +; W503/504: We break lines before binary operators (Knuth's style) [flake8] hang-closing = true -; E128 continuation line under-indented for visual indent -; E251 unexpected spaces around keyword / parameter equals -;F403:'from openfisca_tunisia_pension.model.base import *' used; unable to detect undefined names -;F405:IntCol may be undefined, or defined from star imports: openfisca_tunisia_pension.model.base -ignore = E128,E251,F403,F405 -;max-complexity = 10 -max-line-length = 120 +ignore = E128,E251,F403,F405,E501,W503 +docstring-quotes = single +inline-quotes = single +multiline-quotes = single -# Nose +[pep8] +hang-closing = true +ignore = E128,E251,F403,F405,E501,W503 +in-place = true -[nosetests] -with-doctest = 1 +[tool:pytest] +addopts = --showlocals --exitfirst --doctest-modules --disable-pytest-warnings +testpaths = tests +python_files = **/*.py diff --git a/setup.py b/setup.py index 9d871d1..dfd889c 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #! /usr/bin/env python -# -*- coding: utf-8 -*- """Tunisia Pension specific model for OpenFisca -- a versatile microsimulation free software""" @@ -32,18 +31,20 @@ url = 'https://github.com/openfisca/openfisca-tunisia-pension', data_files = [], - extras_require = dict( - tests = [ - 'nose', + extras_require = { + 'dev': [ + 'autopep8 >=2.0.2, <3.0', + 'flake8 >=6.0.0, <7.0.0', + 'flake8-print >=5.0.0, <6.0.0', + 'flake8-quotes >=3.3.2', + 'pytest >=7.2.2, <8.0', + 'scipy >=1.10.1, <2.0', # Only used to test de_net_a_brut reform + 'requests >=2.28.2, <3.0', + 'yamllint >=1.30.0, <2.0' ], - ), + }, install_requires = [ - 'Babel >= 0.9.4', - 'numpy<1.15,>=1.11', # Attune Bottleneck & Core dependency - 'Bottleneck == 1.2.0', - 'Biryani[datetimeconv] >= 0.10.4', - 'OpenFisca-Core >= 24.0, < 25.0', - 'PyYAML >= 3.10', + 'OpenFisca-Core >= 41.4.1, < 42.0', 'scipy >= 0.12', ], packages = find_packages(exclude=['openfisca_tunisia_pension.tests*']), From 9df937fa2aaec01e3c9a6e6a3e61652c41e41142 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Fri, 19 Jan 2024 15:52:24 +0100 Subject: [PATCH 02/12] Clean --- openfisca_tunisia_pension/model/base.py | 3 -- openfisca_tunisia_pension/model/data.py | 31 +++++++++---------- openfisca_tunisia_pension/model/pension.py | 20 ++++++------ openfisca_tunisia_pension/scenarios.py | 26 ++++++++-------- openfisca_tunisia_pension/tests/base.py | 3 -- .../tests/test_pension.py | 2 +- .../tunisia_pension_taxbenefitsystem.py | 4 +-- setup.py | 6 ++-- 8 files changed, 44 insertions(+), 51 deletions(-) delete mode 100644 openfisca_tunisia_pension/model/base.py diff --git a/openfisca_tunisia_pension/model/base.py b/openfisca_tunisia_pension/model/base.py deleted file mode 100644 index a5d5a9d..0000000 --- a/openfisca_tunisia_pension/model/base.py +++ /dev/null @@ -1,3 +0,0 @@ -from datetime import date -from openfisca_core.model_api import * -from openfisca_tunisia_pension.entities import FoyerFiscal, Individu, Menage diff --git a/openfisca_tunisia_pension/model/data.py b/openfisca_tunisia_pension/model/data.py index 1894133..11aae93 100644 --- a/openfisca_tunisia_pension/model/data.py +++ b/openfisca_tunisia_pension/model/data.py @@ -12,28 +12,28 @@ class date_naissance(Variable): value_type = date default_value = date(1970, 1, 1) entity = Individu - label = u"Date de naissance" + label = 'Date de naissance' definition_period = ETERNITY class salaire(Variable): value_type = float entity = Individu - label = u"Salaires" + label = 'Salaires' definition_period = YEAR class age(Variable): value_type = int entity = Individu - label = u"Âge" + label = 'Âge' definition_period = YEAR class trimestres_valides(Variable): value_type = int entity = Individu - label = u"Nombre de trimestres validés" + label = 'Nombre de trimestres validés' definition_period = YEAR @@ -41,26 +41,25 @@ class TypesRegimeSecuriteSociale(Enum): __order__ = 'rsna rsa rsaa rtns rtte re rtfr raci salarie_cnrps pensionne_cnrps' # Needed to preserve the enum order in Python 2 - rsna = u"Régime des Salariés Non Agricoles" - rsa = u"Régime des Salariés Agricoles" - rsaa = u"Régime des Salariés Agricoles Amélioré" - rtns = u"Régime des Travailleurs Non Salariés (secteurs agricole et non agricole)" - rtte = u"Régime des Travailleurs Tunisiens à l'Etranger" - re = u"Régime des Etudiants, diplômés de l'enseignement supérieur et stagiaires" - rtfr = u"Régime des Travailleurs à Faibles Revenus (gens de maisons, travailleurs de chantiers, et artisans travaillant à la pièce)" - raci = u"Régime des Artistes, Créateurs et Intellectuels" - salarie_cnrps = u"Régime des salariés affilés à la Caisse Nationale de Retraite et de Prévoyance Sociale" - pensionne_cnrps = u"Régime des salariés des pensionnés de la Caisse Nationale de Retraite et de Prévoyance Sociale" + rsna = 'Régime des Salariés Non Agricoles' + rsa = 'Régime des Salariés Agricoles' + rsaa = 'Régime des Salariés Agricoles Amélioré' + rtns = 'Régime des Travailleurs Non Salariés (secteurs agricole et non agricole)' + rtte = "Régime des Travailleurs Tunisiens à l'Etranger" + re = "Régime des Etudiants, diplômés de l'enseignement supérieur et stagiaires" + rtfr = 'Régime des Travailleurs à Faibles Revenus (gens de maisons, travailleurs de chantiers, et artisans travaillant à la pièce)' + raci = 'Régime des Artistes, Créateurs et Intellectuels' + salarie_cnrps = 'Régime des salariés affiliés à la Caisse Nationale de Retraite et de Prévoyance Sociale' + pensionne_cnrps = 'Régime des salariés des pensionnés de la Caisse Nationale de Retraite et de Prévoyance Sociale' # references : # http://www.social.gov.tn/index.php?id=49&L=0 # http://www.paie-tunisie.com/412/fr/83/reglementations/regimes-de-securite-sociale.aspx - class regime_securite_sociale(Variable): value_type = Enum possible_values = TypesRegimeSecuriteSociale default_value = TypesRegimeSecuriteSociale.rsna entity = Individu - label = u"Régime de sécurité sociale du retraité" + label = 'Régime de sécurité sociale du retraité' definition_period = YEAR diff --git a/openfisca_tunisia_pension/model/pension.py b/openfisca_tunisia_pension/model/pension.py index 0bcb872..ec4f827 100644 --- a/openfisca_tunisia_pension/model/pension.py +++ b/openfisca_tunisia_pension/model/pension.py @@ -20,7 +20,7 @@ class salaire_reference_rsa(Variable): value_type = float entity = Individu - label = u"Salaires de référence du régime des salariés agricoles" + label = 'Salaires de référence du régime des salariés agricoles' definition_period = YEAR def formula(individu, period): @@ -36,7 +36,7 @@ def formula(individu, period): mean_over_largest, axis = 0, arr = vstack([ - individu('salaire', period = periods.period("year", year)) + individu('salaire', period = periods.period('year', year)) for year in range(period.start.year, period.start.year - n, -1) ]), ) @@ -47,7 +47,7 @@ def formula(individu, period): class salaire_reference_rsna(Variable): value_type = float entity = Individu - label = u"Salaires de référence du régime des salariés non agricoles" + label = 'Salaires de référence du régime des salariés non agricoles' definition_period = YEAR def formula(individu, period): @@ -69,7 +69,7 @@ def formula(individu, period): class pension_rsna(Variable): value_type = float entity = Individu - label = u"Pension des affiliés au régime des salariés non agricoles" + label = 'Pension des affiliés au régime des salariés non agricoles' definition_period = YEAR def formula(individu, period, parameters): @@ -113,9 +113,9 @@ def formula(individu, period, parameters): def _pension_rsa(trimestres_valides, sal_ref_rsa, regime, age, _P): - """ + ''' Pension du régime des salariés agricoles - """ + ''' taux_annuite_base = _P.pension.rsa.taux_annuite_base taux_annuite_supplemetaire = _P.pension.rsa.taux_annuite_supplemetaire duree_stage = _P.pension.rsa.stage_requis @@ -142,10 +142,10 @@ def _pension_rsa(trimestres_valides, sal_ref_rsa, regime, age, _P): def pension_generique(trimestres_valides, sal_ref, age, taux_annuite_base, taux_annuite_supplemetaire, duree_stage, age_elig, periode_remplacement_base, plaf_taux_pension, smig): taux_pension = ( - (trimestres_valides < 4 * periode_remplacement_base) * (trimestres_valides / 4) * taux_annuite_base + - (trimestres_valides >= 4 * periode_remplacement_base) * ( - taux_annuite_base * periode_remplacement_base + - (trimestres_valides / 4 - periode_remplacement_base) * taux_annuite_supplemetaire + (trimestres_valides < 4 * periode_remplacement_base) * (trimestres_valides / 4) * taux_annuite_base + + (trimestres_valides >= 4 * periode_remplacement_base) * ( + taux_annuite_base * periode_remplacement_base + + (trimestres_valides / 4 - periode_remplacement_base) * taux_annuite_supplemetaire ) ) montant = min_(taux_pension, plaf_taux_pension) * sal_ref diff --git a/openfisca_tunisia_pension/scenarios.py b/openfisca_tunisia_pension/scenarios.py index 5f9d91e..f0fe4cc 100644 --- a/openfisca_tunisia_pension/scenarios.py +++ b/openfisca_tunisia_pension/scenarios.py @@ -78,8 +78,8 @@ def post_process_test_case(self, test_case, period, state): # individu_by_id[individu_id].get('invalide', False) # or find_age(individu_by_id[individu_id], period.start.date, # default = 0) <= 25, - # error = u"Une personne à charge d'un foyer fiscal doit avoir moins de" - # u" 25 ans ou être handicapée", + # error = "Une personne à charge d'un foyer fiscal doit avoir moins de" + # " 25 ans ou être handicapée", # ), # ), # parents = conv.pipe( @@ -115,8 +115,8 @@ def post_process_test_case(self, test_case, period, state): individu_by_id[individu_id].get('handicap', False) or find_age(individu_by_id[individu_id], period.start.date, default = 0) <= 25, - error = u"Une personne à charge d'un foyer fiscal doit avoir moins de" - u" 25 ans ou être handicapée", + error = "Une personne à charge d'un foyer fiscal doit avoir moins de" + " 25 ans ou être handicapée", ), ), ), @@ -403,7 +403,7 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i error = {} for individu_id in remaining_individus_id: error.setdefault('individus', {})[individu_index_by_id[individu_id]] = state._( - u"Individual is missing from {}").format( + "Individual is missing from {}").format( state._(' & ').join( word for word in [ @@ -434,13 +434,13 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i # conv.test(lambda individu_id: # find_age(individu_by_id[individu_id], period.start.date, # default = 100) >= 18, - # error = u"Un déclarant d'un foyer fiscal doit être agé d'au moins 18" - # u" ans", + # error = "Un déclarant d'un foyer fiscal doit être agé d'au moins 18" + # " ans", # ), # conv.test( # lambda individu_id: individu_id in parents_id, - # error = u"Un déclarant ou un conjoint sur la déclaration d'impôt, doit" - # u" être un parent dans sa famille", + # error = "Un déclarant ou un conjoint sur la déclaration d'impôt, doit" + # " être un parent dans sa famille", # ), # )), ), @@ -450,8 +450,8 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i individu_by_id[individu_id].get('invalide', False) or find_age(individu_by_id[individu_id], period.start.date, default = 0) < 25, - error = u"Une personne à charge d'un foyer fiscal doit avoir moins de" - u" 25 ans ou être invalide", + error = "Une personne à charge d'un foyer fiscal doit avoir moins de" + " 25 ans ou être invalide", ), ), ), @@ -466,7 +466,7 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i # dict( # date_naissance = conv.test( # lambda date_naissance: period.start.date - date_naissance >= datetime.timedelta(0), - # error = u"L'individu doit être né au plus tard le jour de la simulation", + # error = "L'individu doit être né au plus tard le jour de la simulation", # ), # ), # default = conv.noop, @@ -494,7 +494,7 @@ def attribute_groupless_persons_to_entities(self, test_case, period, groupless_i return json_or_python_to_test_case def suggest(self): - """Returns a dict of suggestions and modifies self.test_case applying those suggestions.""" + '''Returns a dict of suggestions and modifies self.test_case applying those suggestions.''' test_case = self.test_case if test_case is None: return None diff --git a/openfisca_tunisia_pension/tests/base.py b/openfisca_tunisia_pension/tests/base.py index e2a57e3..dd13316 100644 --- a/openfisca_tunisia_pension/tests/base.py +++ b/openfisca_tunisia_pension/tests/base.py @@ -1,10 +1,7 @@ -from openfisca_core.tools import assert_near - from openfisca_tunisia_pension import TunisiaPensionTaxBenefitSystem __all__ = [ - 'assert_near', 'tax_benefit_system', 'TunisiaPensionTaxBenefitSystem', ] diff --git a/openfisca_tunisia_pension/tests/test_pension.py b/openfisca_tunisia_pension/tests/test_pension.py index 9b99c7b..f8fe104 100644 --- a/openfisca_tunisia_pension/tests/test_pension.py +++ b/openfisca_tunisia_pension/tests/test_pension.py @@ -11,7 +11,7 @@ def test_rsna(): age = 60, trimestres_valides = 50, salaire = dict( - [("{}".format(yr + 1), 12 * 1000) for yr in range(2014 - 40, 2014)] + [('{}'.format(yr + 1), 12 * 1000) for yr in range(2014 - 40, 2014)] ), ), ).new_simulation(debug = True) diff --git a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py index eb4bd54..70e9ec6 100644 --- a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py +++ b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py @@ -11,8 +11,8 @@ class TunisiaPensionTaxBenefitSystem(TaxBenefitSystem): - """Tunisian pensions tax benefit system""" - CURRENCY = u"DT" + '''Tunisian pensions tax benefit system''' + CURRENCY = 'DT' def __init__(self): super(TunisiaPensionTaxBenefitSystem, self).__init__(entities.entities) diff --git a/setup.py b/setup.py index dfd889c..7d75f65 100755 --- a/setup.py +++ b/setup.py @@ -1,19 +1,19 @@ #! /usr/bin/env python -"""Tunisia Pension specific model for OpenFisca -- a versatile microsimulation free software""" +'''Tunisia Pension specific model for OpenFisca -- a versatile microsimulation free software''' from setuptools import setup, find_packages -classifiers = """\ +classifiers = '''\ Development Status :: 2 - Pre-Alpha License :: OSI Approved :: GNU Affero General Public License v3 Operating System :: POSIX Programming Language :: Python Topic :: Scientific/Engineering :: Information Analysis -""" +''' doc_lines = __doc__.split('\n') From 113e2127379848d20bf2841134cad109db232983 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Fri, 19 Jan 2024 15:53:06 +0100 Subject: [PATCH 03/12] Remove scenario --- openfisca_tunisia_pension/scenarios.py | 638 ------------------------- 1 file changed, 638 deletions(-) delete mode 100644 openfisca_tunisia_pension/scenarios.py diff --git a/openfisca_tunisia_pension/scenarios.py b/openfisca_tunisia_pension/scenarios.py deleted file mode 100644 index f0fe4cc..0000000 --- a/openfisca_tunisia_pension/scenarios.py +++ /dev/null @@ -1,638 +0,0 @@ -import collections -import datetime -import itertools -import logging -import re -import uuid - -from openfisca_core import conv, scenarios -from openfisca_tunisia_pension.entities import Individu, FoyerFiscal, Menage - - -def N_(message): - return message - - -log = logging.getLogger(__name__) -# year_or_month_or_day_re = re.compile(ur'(18|19|20)\d{2}(-(0[1-9]|1[0-2])(-([0-2]\d|3[0-1]))?)?$') - - -class Scenario(scenarios.AbstractScenario): - def init_single_entity(self, axes = None, enfants = None, foyer_fiscal = None, menage = None, - parent1 = None, parent2 = None, period = None): - if enfants is None: - enfants = [] - assert parent1 is not None - foyer_fiscal = foyer_fiscal.copy() if foyer_fiscal is not None else {} - individus = [] - menage = menage.copy() if menage is not None else {} - for index, individu in enumerate([parent1, parent2] + (enfants or [])): - if individu is None: - continue - id = individu.get('id') - if id is None: - individu = individu.copy() - individu['id'] = id = 'ind{}'.format(index) - individus.append(individu) - if index <= 1: - foyer_fiscal.setdefault('declarants', []).append(id) - if index == 0: - menage['personne_de_reference'] = id - else: - menage['conjoint'] = id - else: - foyer_fiscal.setdefault('personnes_a_charge', []).append(id) - menage.setdefault('enfants', []).append(id) - conv.check(self.make_json_or_python_to_attributes())(dict( - axes = axes, - period = period, - test_case = dict( - foyers_fiscaux = [foyer_fiscal], - individus = individus, - menages = [menage], - ), - )) - return self - - def post_process_test_case(self, test_case, period, state): - - individu_by_id = { - individu['id']: individu - for individu in test_case['individus'] - } - - parents_id = set( - parent_id - for foyer_fiscal in test_case['foyers_fiscaux'] - for parent_id in foyer_fiscal['declarants'] - ) - test_case, error = conv.struct( - dict( - # foyers_fiscaux = conv.pipe( - # conv.uniform_sequence( - # conv.struct( - # dict( - # enfants = conv.uniform_sequence( - # conv.test( - # lambda individu_id: - # individu_by_id[individu_id].get('invalide', False) - # or find_age(individu_by_id[individu_id], period.start.date, - # default = 0) <= 25, - # error = "Une personne à charge d'un foyer fiscal doit avoir moins de" - # " 25 ans ou être handicapée", - # ), - # ), - # parents = conv.pipe( - # conv.empty_to_none, - # conv.not_none, - # conv.test(lambda parents: len(parents) <= 2, - # error = N_('A "famille" must have at most 2 "parents"')) - # ), - # ), - # default = conv.noop, - # ), - # ), - # conv.empty_to_none, - # conv.not_none, - # ), - foyers_fiscaux = conv.pipe( - conv.uniform_sequence( - conv.struct( - dict( - declarants = conv.pipe( - conv.empty_to_none, - conv.not_none, - conv.test( - lambda declarants: len(declarants) <= 2, - error = N_('A "foyer_fiscal" must have at most 2 "declarants"'), - ), - conv.uniform_sequence(conv.pipe( - )), - ), - personnes_a_charge = conv.uniform_sequence( - conv.test( - lambda individu_id: - individu_by_id[individu_id].get('handicap', False) - or find_age(individu_by_id[individu_id], period.start.date, - default = 0) <= 25, - error = "Une personne à charge d'un foyer fiscal doit avoir moins de" - " 25 ans ou être handicapée", - ), - ), - ), - default = conv.noop, - ), - ), - conv.empty_to_none, - conv.not_none, - ), - menages = conv.pipe( - conv.uniform_sequence( - conv.struct( - dict( - personne_de_reference = conv.not_none, - ), - default = conv.noop, - ), - ), - conv.empty_to_none, - conv.not_none, - ), - ), - default = conv.noop, - )(test_case, state = state) - - return test_case, error - # First validation and conversion step - test_case, error = conv.pipe( - conv.test_isinstance(dict), - conv.struct( - dict( - foyers_fiscaux = conv.pipe( - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance(dict), - drop_none_items = True, - ), - conv.uniform_sequence( - conv.struct( - dict(itertools.chain( - dict( - declarants = conv.pipe( - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance((basestring, int)), - drop_none_items = True, - ), - conv.default([]), - ), - id = conv.pipe( - conv.test_isinstance((basestring, int)), - conv.not_none, - ), - personnes_a_charge = conv.pipe( - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance((basestring, int)), - drop_none_items = True, - ), - conv.default([]), - ), - ).items(), - )), - drop_none_values = True, - ), - drop_none_items = True, - ), - conv.default([]), - ), - menages = conv.pipe( - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance(dict), - drop_none_items = True, - ), - conv.uniform_sequence( - conv.struct( - dict(itertools.chain( - dict( - autres = conv.pipe( - # personnes ayant un lien autre avec la personne de référence - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance((basestring, int)), - drop_none_items = True, - ), - conv.default([]), - ), - # conjoint de la personne de référence - conjoint = conv.test_isinstance((basestring, int)), - enfants = conv.pipe( - # enfants de la personne de référence ou de son conjoint - conv.make_item_to_singleton(), - conv.test_isinstance(list), - conv.uniform_sequence( - conv.test_isinstance((basestring, int)), - drop_none_items = True, - ), - conv.default([]), - ), - id = conv.pipe( - conv.test_isinstance((basestring, int)), - conv.not_none, - ), - personne_de_reference = conv.test_isinstance((basestring, int)), - ).items(), - )), - drop_none_values = True, - ), - drop_none_items = True, - ), - conv.default([]), - ), - ), - ), - )(test_case, state = state) - - # test_case, error = conv.struct( - # dict( - # foyers_fiscaux = conv.uniform_sequence( - # conv.struct( - # dict( - # declarants = conv.uniform_sequence(conv.test_in_pop(foyers_fiscaux_individus_id)), - # personnes_a_charge = conv.uniform_sequence(conv.test_in_pop( - # foyers_fiscaux_individus_id)), - # ), - # default = conv.noop, - # ), - # ), - # menages = conv.uniform_sequence( - # conv.struct( - # dict( - # autres = conv.uniform_sequence(conv.test_in_pop(menages_individus_id)), - # conjoint = conv.test_in_pop(menages_individus_id), - # enfants = conv.uniform_sequence(conv.test_in_pop(menages_individus_id)), - # personne_de_reference = conv.test_in_pop(menages_individus_id), - # ), - # default = conv.noop, - # ), - # ), - # ), - # default = conv.noop, - # )(test_case, state = state) - - return test_case, error - - def attribute_groupless_persons_to_entities(self, test_case, period, groupless_individus): - individus_without_menage = groupless_individus['menages'] - individus_without_foyer_fiscal = groupless_individus['foyers_fiscaux'] - - individu_by_id = { - individu['id']: individu - for individu in test_case['individus'] - } - - # Affecte à un foyer fiscal chaque individu qui n'appartient à aucun d'entre eux. - new_foyer_fiscal = dict( - declarants = [], - personnes_a_charge = [], - ) - new_foyer_fiscal_id = None - for individu_id in individus_without_foyer_fiscal[:]: - # Tente d'affecter l'individu à un foyer fiscal d'après son ménage. - menage, menage_role = find_menage_and_role(test_case, individu_id) - if menage_role == 'personne_de_reference': - conjoint_id = menage['conjoint'] - if conjoint_id is not None: - foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, conjoint_id) - if other_role == 'declarants' and len(foyer_fiscal['declarants']) == 1: - # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est personne de - # référence dans un ménage, q'il y a un conjoint dans ce ménage et que ce - # conjoint est seul déclarant dans un foyer fiscal, alors ajoute l'individu comme - # autre déclarant de ce foyer fiscal. - foyer_fiscal['declarants'].append(individu_id) - individus_without_foyer_fiscal.remove(individu_id) - elif menage_role == 'conjoint': - personne_de_reference_id = menage['personne_de_reference'] - if personne_de_reference_id is not None: - foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, personne_de_reference_id) - if other_role == 'declarants' and len(foyer_fiscal['declarants']) == 1: - # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est conjoint - # dans un ménage, q'il y a une personne de référence dans ce ménage et que - # cette personne est seul déclarant dans un foyer fiscal, alors ajoute l'individu - # comme autre déclarant de ce foyer fiscal. - foyer_fiscal['declarants'].append(individu_id) - individus_without_foyer_fiscal.remove(individu_id) - elif menage_role == 'enfants' and ( - menage['personne_de_reference'] is not None or menage['conjoint'] is not None): - for other_id in (menage['personne_de_reference'], menage['conjoint']): - if other_id is None: - continue - foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, other_id) - if other_role == 'declarants': - # Quand l'individu n'est pas encore dans un foyer fiscal, mais q'il est enfant dans - # un ménage, q'il y a une personne à charge ou un conjoint dans ce ménage et que - # celui-ci est déclarant dans un foyer fiscal, alors ajoute l'individu comme - # personne à charge de ce foyer fiscal. - foyer_fiscal['personnes_a_charge'].append(individu_id) - individus_without_foyer_fiscal.remove(individu_id) - break - - if individu_id in individus_without_foyer_fiscal: - # L'individu n'est toujours pas affecté à un foyer fiscal. - individu = individu_by_id[individu_id] - age = find_age(individu, period.start.date) - if len(new_foyer_fiscal['declarants']) < 2 and (age is None or age >= 18): - new_foyer_fiscal['declarants'].append(individu_id) - else: - new_foyer_fiscal['personnes_a_charge'].append(individu_id) - if new_foyer_fiscal_id is None: - new_foyer_fiscal['id'] = new_foyer_fiscal_id = unicode(uuid.uuid4()) - test_case['foyers_fiscaux'].append(new_foyer_fiscal) - individus_without_foyer_fiscal.remove(individu_id) - - # Affecte à un ménage chaque individu qui n'appartient à aucun d'entre eux. - new_menage = dict( - autres = [], - conjoint = None, - enfants = [], - personne_de_reference = None, - ) - new_menage_id = None - for individu_id in menages_individus_id[:]: - # Tente d'affecter l'individu à un ménage d'après son foyer fiscal. - foyer_fiscal, foyer_fiscal_role = find_foyer_fiscal_and_role(test_case, individu_id) - if foyer_fiscal_role == 'declarants' and len(foyer_fiscal['declarants']) == 2: - for declarant_id in foyer_fiscal['declarants']: - if declarant_id != individu_id: - menage, other_role = find_menage_and_role(test_case, declarant_id) - if other_role == 'personne_de_reference' and menage['conjoint'] is None: - # Quand l'individu n'est pas encore dans un ménage, mais q'il est déclarant - # dans un foyer fiscal, q'il y a un autre déclarant dans ce foyer fiscal et que - # cet autre déclarant est personne de référence dans un ménage et q'il n'y a - # pas de conjoint dans ce ménage, alors ajoute l'individu comme conjoint de ce - # ménage. - menage['conjoint'] = individu_id - menages_individus_id.remove(individu_id) - elif other_role == 'conjoint' and menage['personne_de_reference'] is None: - # Quand l'individu n'est pas encore dans un ménage, mais q'il est déclarant - # dans une foyer fiscal, q'il y a un autre déclarant dans ce foyer fiscal et - # que cet autre déclarant est conjoint dans un ménage et q'il n'y a pas de - # personne de référence dans ce ménage, alors ajoute l'individu comme personne - # de référence de ce ménage. - menage['personne_de_reference'] = individu_id - menages_individus_id.remove(individu_id) - break - elif foyer_fiscal_role == 'personnes_a_charge' and foyer_fiscal['declarants']: - for declarant_id in foyer_fiscal['declarants']: - menage, other_role = find_menage_and_role(test_case, declarant_id) - if other_role in ('personne_de_reference', 'conjoint'): - # Quand l'individu n'est pas encore dans un ménage, mais q'il est personne à charge - # dans un foyer fiscal, q'il y a un déclarant dans ce foyer fiscal et que ce - # déclarant est personne de référence ou conjoint dans un ménage, alors ajoute - # l'individu comme enfant de ce ménage. - menage['enfants'].append(individu_id) - menages_individus_id.remove(individu_id) - break - - if individu_id in menages_individus_id: - # L'individu n'est toujours pas affecté à un ménage. - if new_menage['personne_de_reference'] is None: - new_menage['personne_de_reference'] = individu_id - elif new_menage['conjoint'] is None: - new_menage['conjoint'] = individu_id - else: - new_menage['enfants'].append(individu_id) - if new_menage_id is None: - new_menage['id'] = new_menage_id = unicode(uuid.uuid4()) - test_case['menages'].append(new_menage) - menages_individus_id.remove(individu_id) - - remaining_individus_id = set(individus_without_foyer_fiscal).union(menages_individus_id) - if remaining_individus_id: - individu_index_by_id = { - individu['id']: individu_index - for individu_index, individu in enumerate(test_case['individus']) - } - if error is None: - error = {} - for individu_id in remaining_individus_id: - error.setdefault('individus', {})[individu_index_by_id[individu_id]] = state._( - "Individual is missing from {}").format( - state._(' & ').join( - word - for word in [ - 'foyers_fiscaux' if individu_id in individus_without_foyer_fiscal else None, - 'menages' if individu_id in menages_individus_id else None, - ] - if word is not None - )) - if error is not None: - return test_case, error - - # Third validation step - individu_by_id = test_case['individus'] - test_case, error = conv.struct( - dict( - foyers_fiscaux = conv.pipe( - conv.uniform_sequence( - conv.struct( - dict( - declarants = conv.pipe( - conv.empty_to_none, - conv.not_none, - conv.test( - lambda declarants: len(declarants) <= 2, - error = N_('A "foyer_fiscal" must have at most 2 "declarants"'), - ), - # conv.uniform_sequence(conv.pipe( - # conv.test(lambda individu_id: - # find_age(individu_by_id[individu_id], period.start.date, - # default = 100) >= 18, - # error = "Un déclarant d'un foyer fiscal doit être agé d'au moins 18" - # " ans", - # ), - # conv.test( - # lambda individu_id: individu_id in parents_id, - # error = "Un déclarant ou un conjoint sur la déclaration d'impôt, doit" - # " être un parent dans sa famille", - # ), - # )), - ), - personnes_a_charge = conv.uniform_sequence( - conv.test( - lambda individu_id: - individu_by_id[individu_id].get('invalide', False) - or find_age(individu_by_id[individu_id], period.start.date, - default = 0) < 25, - error = "Une personne à charge d'un foyer fiscal doit avoir moins de" - " 25 ans ou être invalide", - ), - ), - ), - default = conv.noop, - ), - ), - conv.empty_to_none, - conv.not_none, - ), - # individus = conv.uniform_sequence( - # conv.struct( - # dict( - # date_naissance = conv.test( - # lambda date_naissance: period.start.date - date_naissance >= datetime.timedelta(0), - # error = "L'individu doit être né au plus tard le jour de la simulation", - # ), - # ), - # default = conv.noop, - # drop_none_values = 'missing', - # ), - # ), - menages = conv.pipe( - conv.uniform_sequence( - conv.struct( - dict( - personne_de_reference = conv.not_none, - ), - default = conv.noop, - ), - ), - conv.empty_to_none, - conv.not_none, - ), - ), - default = conv.noop, - )(test_case, state = state) - - return test_case, error - - return json_or_python_to_test_case - - def suggest(self): - '''Returns a dict of suggestions and modifies self.test_case applying those suggestions.''' - test_case = self.test_case - if test_case is None: - return None - - period_start_date = self.period.start.date - period_start_year = self.period.start.year - suggestions = dict() - - for individu in test_case['individus']: - individu_id = individu['id'] - if ( - individu.get('age') is None and - individu.get('date_naissance') is None - ): - # Add missing date_naissance date to person (a parent is 40 years old and a child is 10 years old. - is_declarant = any( - individu_id in foyer_fiscal['declarants'] - for foyer_fiscal in test_case['foyers_fiscaux'] - ) - date_naissance_year = period_start_year - 40 if is_declarant else period_start_year - 10 - date_naissance = datetime.date(date_naissance_year, 1, 1) - individu['date_naissance'] = date_naissance - suggestions.setdefault('test_case', {}).setdefault('individus', {}).setdefault(individu_id, {})[ - 'date_naissance'] = date_naissance.isoformat() - - return suggestions or None - - def to_json(self): - self_json = collections.OrderedDict() - if self.axes is not None: - self_json['axes'] = self.axes - if self.period is not None: - self_json['period'] = str(self.period) - - test_case = self.test_case - if test_case is not None: - column_by_name = self.tax_benefit_system.column_by_name - test_case_json = collections.OrderedDict() - - foyers_fiscaux_json = [] - for foyer_fiscal in (test_case.get('foyers_fiscaux') or []): - foyer_fiscal_json = collections.OrderedDict() - foyer_fiscal_json['id'] = foyer_fiscal['id'] - declarants = foyer_fiscal.get('declarants') - if declarants: - foyer_fiscal_json['declarants'] = declarants - personnes_a_charge = foyer_fiscal.get('personnes_a_charge') - if personnes_a_charge: - foyer_fiscal_json['personnes_a_charge'] = personnes_a_charge - for column_name, variable_value in foyer_fiscal.items(): - column = column_by_name.get(column_name) - if column is not None and column.entity == FoyerFiscal: - variable_value_json = column.transform_value_to_json(variable_value) - if variable_value_json is not None: - foyer_fiscal_json[column_name] = variable_value_json - foyers_fiscaux_json.append(foyer_fiscal_json) - if foyers_fiscaux_json: - test_case_json['foyers_fiscaux'] = foyers_fiscaux_json - - individus_json = [] - for individu in (test_case.get('individus') or []): - individu_json = collections.OrderedDict() - for column_name, variable_value in individu.items(): - column = column_by_name.get(column_name) - if column is not None and column.entity == Individu: - variable_value_json = column.transform_value_to_json(variable_value) - if variable_value_json is not None: - individu_json[column_name] = variable_value_json - individus_json.append(individu_json) - if individus_json: - test_case_json['individus'] = individus_json - - menages_json = [] - for menage in (test_case.get('menages') or []): - menage_json = collections.OrderedDict() - menage_json['id'] = menage['id'] - personne_de_reference = menage.get('personne_de_reference') - if personne_de_reference is not None: - menage_json['personne_de_reference'] = personne_de_reference - conjoint = menage.get('conjoint') - if conjoint is not None: - menage_json['conjoint'] = conjoint - enfants = menage.get('enfants') - if enfants: - menage_json['enfants'] = enfants - autres = menage.get('autres') - if autres: - menage_json['autres'] = autres - for column_name, variable_value in menage.items(): - column = column_by_name.get(column_name) - if column is not None and column.entity == Menage: - variable_value_json = column.transform_value_to_json(variable_value) - if variable_value_json is not None: - menage_json[column_name] = variable_value_json - menages_json.append(menage_json) - if menages_json: - test_case_json['menages'] = menages_json - - self_json['test_case'] = test_case_json - return self_json - - -# Finders - - -def find_foyer_fiscal_and_role(test_case, individu_id): - for foyer_fiscal in test_case['foyers_fiscaux']: - for role in ('declarants', 'personnes_a_charge'): - if individu_id in foyer_fiscal[role]: - return foyer_fiscal, role - return None, None - - -def find_menage_and_role(test_case, individu_id): - for menage in test_case['menages']: - for role in ('personne_de_reference', 'conjoint'): - if menage[role] == individu_id: - return menage, role - for role in ('enfants', 'autres'): - if individu_id in menage[role]: - return menage, role - return None, None - - -def find_age(individu, date, default = None): - date_naissance = individu.get('date_naissance') - if date_naissance is not None: - age = date.year - date_naissance.year - if date.month < date_naissance.month or date.month == date_naissance.month and date.day < date_naissance.day: - age -= 1 - return age - age = individu.get('age') - if age is not None: - return age - age = individu.get('age') - if age is not None: - return age - agem = individu.get('agem') - if agem is not None: - return agem / 12.0 - return default From 91925fdeef87dcfbfafcaf186bb892faa9b056e2 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Fri, 19 Jan 2024 18:19:16 +0100 Subject: [PATCH 04/12] More cleaning --- Makefile | 2 +- openfisca_tunisia_pension/model/data.py | 3 ++- openfisca_tunisia_pension/model/pension.py | 7 +++--- .../tests/formulas/pension_rsna.yaml | 6 ++--- .../formulas/salaire_reference_rsna.yaml | 6 ++--- .../tests/test_pension.py | 24 ------------------- .../tunisia_pension_taxbenefitsystem.py | 3 +-- setup.py | 1 + 8 files changed, 14 insertions(+), 38 deletions(-) delete mode 100644 openfisca_tunisia_pension/tests/test_pension.py diff --git a/Makefile b/Makefile index 2ebe30a..333cd28 100644 --- a/Makefile +++ b/Makefile @@ -50,4 +50,4 @@ check-all-yaml: test: clean check-syntax-errors check-style @# Launch tests from openfisca_tunisia_pension/tests directory (and not .) because TaxBenefitSystem must be initialized @# before parsing source files containing formulas. - openfisca test --country-package openfisca_tunisia_pension tests + openfisca test --country-package openfisca_tunisia_pension openfisca_tunisia_pension/tests diff --git a/openfisca_tunisia_pension/model/data.py b/openfisca_tunisia_pension/model/data.py index 11aae93..c2942d8 100644 --- a/openfisca_tunisia_pension/model/data.py +++ b/openfisca_tunisia_pension/model/data.py @@ -1,5 +1,6 @@ -from openfisca_tunisia_pension.model.base import * +from openfisca_core.model_api import * +from openfisca_tunisia_pension.entities import Individu # raic -> raci diff --git a/openfisca_tunisia_pension/model/pension.py b/openfisca_tunisia_pension/model/pension.py index ec4f827..cd108c7 100644 --- a/openfisca_tunisia_pension/model/pension.py +++ b/openfisca_tunisia_pension/model/pension.py @@ -1,8 +1,6 @@ -# -*- coding:utf-8 -*- - - from __future__ import division + import bottleneck import functools from numpy import ( @@ -14,7 +12,8 @@ ) from openfisca_core import periods -from openfisca_tunisia_pension.model.base import * # noqa +from openfisca_core.model_api import * +from openfisca_tunisia_pension.entities import Individu class salaire_reference_rsa(Variable): diff --git a/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml b/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml index d7f5bae..7b38118 100644 --- a/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml +++ b/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml @@ -1,7 +1,7 @@ - name: "Individu salarié 12000 DT par an toute sa carrière" period: 2011 absolute_error_margin: 0.5 - input_variables: + input: age: 60 trimestres_valides: 50 salaire: @@ -45,6 +45,6 @@ 2012: 12000 2013: 12000 2014: 12000 - output_variables: + output: salaire_reference_rsna: 12000 - pension_rsna: 5400 \ No newline at end of file + pension_rsna: 5400 diff --git a/openfisca_tunisia_pension/tests/formulas/salaire_reference_rsna.yaml b/openfisca_tunisia_pension/tests/formulas/salaire_reference_rsna.yaml index 678fcf0..a763fd9 100644 --- a/openfisca_tunisia_pension/tests/formulas/salaire_reference_rsna.yaml +++ b/openfisca_tunisia_pension/tests/formulas/salaire_reference_rsna.yaml @@ -1,7 +1,7 @@ - name: "Individu salarié 12000 DT par an toute sa carrière" period: 2011 absolute_error_margin: 0.5 - input_variables: + input: age: 60 trimestres_valides: 50 salaire: @@ -45,5 +45,5 @@ 2012: 12000 2013: 12000 2014: 12000 - output_variables: - salaire_reference_rsna: 12000 \ No newline at end of file + output: + salaire_reference_rsna: 12000 diff --git a/openfisca_tunisia_pension/tests/test_pension.py b/openfisca_tunisia_pension/tests/test_pension.py deleted file mode 100644 index f8fe104..0000000 --- a/openfisca_tunisia_pension/tests/test_pension.py +++ /dev/null @@ -1,24 +0,0 @@ -from openfisca_core import periods -from openfisca_core.tools import assert_near -from openfisca_tunisia_pension.tests import base - - -def test_rsna(): - year = 2011 - simulation = base.tax_benefit_system.new_scenario().init_single_entity( - period = periods.period(year), - parent1 = dict( - age = 60, - trimestres_valides = 50, - salaire = dict( - [('{}'.format(yr + 1), 12 * 1000) for yr in range(2014 - 40, 2014)] - ), - ), - ).new_simulation(debug = True) - - assert_near(simulation.calculate_add('salaire_reference_rsna', period = year), 12000, .001) - assert_near(simulation.calculate_add('pension_rsna', period = year), 5400, 1) - - -if __name__ == '__main__': - test_rsna() diff --git a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py index 70e9ec6..a45d20e 100644 --- a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py +++ b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py @@ -3,7 +3,7 @@ from openfisca_core.taxbenefitsystems import TaxBenefitSystem -from . import entities, scenarios +from openfisca_tunisia_pension import entities COUNTRY_DIR = os.path.dirname(os.path.abspath(__file__)) EXTENSIONS_PATH = os.path.join(COUNTRY_DIR, 'extensions') @@ -16,7 +16,6 @@ class TunisiaPensionTaxBenefitSystem(TaxBenefitSystem): def __init__(self): super(TunisiaPensionTaxBenefitSystem, self).__init__(entities.entities) - self.Scenario = scenarios.Scenario # We add to our tax and benefit system all the variables self.add_variables_from_directory(os.path.join(COUNTRY_DIR, 'model')) diff --git a/setup.py b/setup.py index 7d75f65..57811d8 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ ], }, install_requires = [ + 'bottleneck >=1.3.2,<=2.0.0', 'OpenFisca-Core >= 41.4.1, < 42.0', 'scipy >= 0.12', ], From 630c8643ac38b7c746d853624d05503dfec2e769 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Wed, 24 Jan 2024 15:50:15 +0100 Subject: [PATCH 05/12] Clean --- openfisca_tunisia_pension/model/pension.py | 20 +++++++++---------- .../{param_gen => marche_travail}/index.yaml | 0 .../{param_gen => marche_travail}/smag.yaml | 0 .../smig_40h.yaml | 0 .../smig_48h.yaml | 0 .../tests/formulas/pension_rsna.yaml | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename openfisca_tunisia_pension/parameters/{param_gen => marche_travail}/index.yaml (100%) rename openfisca_tunisia_pension/parameters/{param_gen => marche_travail}/smag.yaml (100%) rename openfisca_tunisia_pension/parameters/{param_gen => marche_travail}/smig_40h.yaml (100%) rename openfisca_tunisia_pension/parameters/{param_gen => marche_travail}/smig_48h.yaml (100%) diff --git a/openfisca_tunisia_pension/model/pension.py b/openfisca_tunisia_pension/model/pension.py index cd108c7..2a07b13 100644 --- a/openfisca_tunisia_pension/model/pension.py +++ b/openfisca_tunisia_pension/model/pension.py @@ -82,7 +82,7 @@ def formula(individu, period, parameters): age_eligible = parameters(period).pension.rsna.age_dep_anticip periode_remplacement_base = parameters(period).pension.rsna.periode_remplacement_base plaf_taux_pension = parameters(period).pension.rsna.plaf_taux_pension - smig = parameters(period).param_gen.smig_48h + smig = parameters(period).marche_travail.smig_48h pension_min_sup = parameters(period).pension.rsna.pension_minimale.sup pension_min_inf = parameters(period).pension.rsna.pension_minimale.inf @@ -111,19 +111,19 @@ def formula(individu, period, parameters): return eligibilite * montant_pension_percu -def _pension_rsa(trimestres_valides, sal_ref_rsa, regime, age, _P): +def _pension_rsa(trimestres_valides, sal_ref_rsa, regime, age, parameters): ''' Pension du régime des salariés agricoles ''' - taux_annuite_base = _P.pension.rsa.taux_annuite_base - taux_annuite_supplemetaire = _P.pension.rsa.taux_annuite_supplemetaire - duree_stage = _P.pension.rsa.stage_requis - age_elig = _P.pension.rsa.age_legal - periode_remplacement_base = _P.pension.rsa.periode_remplacement_base - plaf_taux_pension = _P.pension.rsa.plaf_taux_pension - smag = _P.param_gen.smag * 25 + taux_annuite_base = parameters.pension.rsa.taux_annuite_base + taux_annuite_supplemetaire = parameters.pension.rsa.taux_annuite_supplemetaire + duree_stage = parameters.pension.rsa.stage_requis + age_elig = parameters.pension.rsa.age_legal + periode_remplacement_base = parameters.pension.rsa.periode_remplacement_base + plaf_taux_pension = parameters.pension.rsa.plaf_taux_pension + smag = parameters.marche_travail.smag * 25 stage = trimestres_valides > 4 * duree_stage - pension_min = _P.pension.rsa.pension_min + pension_min = parameters.pension.rsa.pension_min sal_ref = sal_ref_rsa montant = pension_generique(trimestres_valides, sal_ref, age, taux_annuite_base, taux_annuite_supplemetaire, diff --git a/openfisca_tunisia_pension/parameters/param_gen/index.yaml b/openfisca_tunisia_pension/parameters/marche_travail/index.yaml similarity index 100% rename from openfisca_tunisia_pension/parameters/param_gen/index.yaml rename to openfisca_tunisia_pension/parameters/marche_travail/index.yaml diff --git a/openfisca_tunisia_pension/parameters/param_gen/smag.yaml b/openfisca_tunisia_pension/parameters/marche_travail/smag.yaml similarity index 100% rename from openfisca_tunisia_pension/parameters/param_gen/smag.yaml rename to openfisca_tunisia_pension/parameters/marche_travail/smag.yaml diff --git a/openfisca_tunisia_pension/parameters/param_gen/smig_40h.yaml b/openfisca_tunisia_pension/parameters/marche_travail/smig_40h.yaml similarity index 100% rename from openfisca_tunisia_pension/parameters/param_gen/smig_40h.yaml rename to openfisca_tunisia_pension/parameters/marche_travail/smig_40h.yaml diff --git a/openfisca_tunisia_pension/parameters/param_gen/smig_48h.yaml b/openfisca_tunisia_pension/parameters/marche_travail/smig_48h.yaml similarity index 100% rename from openfisca_tunisia_pension/parameters/param_gen/smig_48h.yaml rename to openfisca_tunisia_pension/parameters/marche_travail/smig_48h.yaml diff --git a/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml b/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml index 7b38118..543aaf5 100644 --- a/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml +++ b/openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml @@ -1,4 +1,4 @@ -- name: "Individu salarié 12000 DT par an toute sa carrière" +- name: "Individu salarié 12000 DT par an durant toute sa carrière" period: 2011 absolute_error_margin: 0.5 input: From 9da6f35ed501978e3797d6c4e62d19bc624e5080 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:09:49 +0100 Subject: [PATCH 06/12] Use github actions --- .github/ISSUE_TEMPLATE.md | 25 +++ .github/PULL_REQUEST_TEMPLATE.md | 31 ++++ .github/dependabot.yml | 9 + .github/get_minimal_version.py | 14 ++ .github/get_pypi_info.py | 51 +++++ .github/has-functional-changes.sh | 12 ++ .github/is-version-number-acceptable.sh | 45 +++++ .github/lint-changed-python-files.sh | 11 ++ .github/lint-changed-yaml-tests.sh | 11 ++ .github/publish-git-tag.sh | 5 + .github/pyproject_version.py | 82 ++++++++ .github/split_tests.py | 22 +++ .github/test-api.sh | 14 ++ .github/workflows/tax-benefit.yml | 93 ++++++++++ .github/workflows/validate_yaml.yml | 15 ++ .github/workflows/workflow.yml | 236 ++++++++++++++++++++++++ README.md | 12 +- pyproject.toml | 86 +++++++++ 18 files changed, 768 insertions(+), 6 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/get_minimal_version.py create mode 100755 .github/get_pypi_info.py create mode 100755 .github/has-functional-changes.sh create mode 100755 .github/is-version-number-acceptable.sh create mode 100755 .github/lint-changed-python-files.sh create mode 100755 .github/lint-changed-yaml-tests.sh create mode 100755 .github/publish-git-tag.sh create mode 100644 .github/pyproject_version.py create mode 100644 .github/split_tests.py create mode 100755 .github/test-api.sh create mode 100644 .github/workflows/tax-benefit.yml create mode 100644 .github/workflows/validate_yaml.yml create mode 100644 .github/workflows/workflow.yml create mode 100644 pyproject.toml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..d952357 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,25 @@ +Hello hello ! + +Je suis le fan numéro un d'OpenFisca, mais je viens de rencontrer un problème. + +### Qu'ai-je fait ? + + +### À quoi m'attendais-je ? + + +### Que s'est-il passé en réalité ? + + +### Voici des informations qui peuvent aider à reproduire le problème : + + +### Contexte + +Je m'identifie plus en tant que : + +- [ ] Contributeur·e : je contribue à OpenFisca Tunisia-Pension. +- [ ] Développeur·e : je crée des outils qui utilisent OpenFisca Tunisia-Pension. +- [ ] Économiste : je réalise des simulations avec des données. +- [ ] Mainteneur·e : j'intègre les contributions à OpenFisca Tunisia-Pension. +- [ ] Autre : _(ajoutez une description du contexte dans lequel vous utilisez OpenFisca)_. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e73a542 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +Merci de contribuer à OpenFisca ! Effacez cette ligne ainsi que, pour chaque ligne ci-dessous, les cas ne correspondant pas à votre contribution :) + +* Évolution du système socio-fiscal. | Amélioration technique. | Correction d'un crash. | Changement mineur. +* Périodes concernées : toutes. | jusqu'au JJ/MM/AAAA. | à partir du JJ/MM/AAAA. +* Zones impactées : `chemin/vers/le/fichier/contenant/les/variables/impactées`. +* Détails : + - Description de la fonctionnalité ajoutée ou du nouveau comportement adopté. + - Cas dans lesquels une erreur était constatée. + +- - - - + +Ces changements (effacez les lignes ne correspondant pas à votre cas) : + +- Modifient l'API publique d'OpenFisca Tunisia-Pension (par exemple renommage ou suppression de variables). +- Ajoutent une fonctionnalité (par exemple ajout d'une variable). +- Corrigent ou améliorent un calcul déjà existant. +- Modifient des éléments non fonctionnels de ce dépôt (par exemple modification du README). + +- - - - + +Quelques conseils à prendre en compte : + +- [ ] Jetez un coup d'œil au [guide de contribution](https://github.com/openfisca/openfisca-tunisia-pension-pension/blob/master/CONTRIBUTING.md). +- [ ] Regardez s'il n'y a pas une [proposition introduisant ces mêmes changements](https://github.com/openfisca/openfisca-tunisia-pension/pulls). +- [ ] Documentez votre contribution avec des références législatives. +- [ ] Mettez à jour ou ajoutez des tests correspondant à votre contribution. +- [ ] Augmentez le [numéro de version](https://speakerdeck.com/mattisg/git-session-2-strategies?slide=81) dans [`setup.py`](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/setup.py). +- [ ] Mettez à jour le [`CHANGELOG.md`](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/CHANGELOG.md). +- [ ] Assurez-vous de bien décrire votre contribution, comme indiqué ci-dessus + +Et surtout, n'hésitez pas à demander de l'aide ! :) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5d9df5c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: monthly + labels: + - kind:dependencies + open-pull-requests-limit: 2 diff --git a/.github/get_minimal_version.py b/.github/get_minimal_version.py new file mode 100644 index 0000000..f4c431c --- /dev/null +++ b/.github/get_minimal_version.py @@ -0,0 +1,14 @@ +import re +import tomli +# This script prints the minimal version of Openfisca-Core to ensure their compatibility during CI testing +with open('./pyproject.toml', 'rb') as file: + config = tomli.load(file) + deps = config['project']['dependencies'] + for dep in deps: + version = re.search(r'openfisca-core\[([^\]]+)\]\s*>=\s*([\d\.]*)', dep) + if version: + try: + print(f'openfisca-core[{version[1]}]=={version[2]}') # noqa: T201 <- This is to avoid flake8 print detection. + except Exception as e: + print(f'Error processing "{dep}": {e}') # noqa: T201 <- This is to avoid flake8 print detection. + exit(1) diff --git a/.github/get_pypi_info.py b/.github/get_pypi_info.py new file mode 100755 index 0000000..87522f6 --- /dev/null +++ b/.github/get_pypi_info.py @@ -0,0 +1,51 @@ +import argparse +import requests +import logging + + +logging.basicConfig(level=logging.INFO) + + +def get_info(package_name: str = '') -> dict: + ''' + Get minimal informations needed by .conda/meta.yaml from PyPi JSON API. + ::package_name:: Name of package to get infos from. + ::return:: A dict with last_version, url and sha256 + ''' + if package_name == '': + raise ValueError('Package name not provided.') + resp = requests.get(f'https://pypi.org/pypi/{package_name}/json').json() + version = resp['info']['version'] + for v in resp['releases'][version]: + if v['packagetype'] == 'sdist': # for .tag.gz + return { + 'last_version': version, + 'url': v['url'], + 'sha256': v['digests']['sha256'] + } + + +def replace_in_file(filepath: str, info: dict): + ''' + ::filepath:: Path to meta.yaml, with filename + ::info:: Dict with information to populate + ''' + with open(filepath, 'rt') as fin: + meta = fin.read() + # Replace with info from PyPi + meta = meta.replace('PYPI_VERSION', info['last_version']) + meta = meta.replace('PYPI_URL', info['url']) + meta = meta.replace('PYPI_SHA256', info['sha256']) + with open(filepath, 'wt') as fout: + fout.write(meta) + logging.info(f'File {filepath} has been updated with informations from PyPi.') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-p', '--package', type=str, default='', required=True, help='The name of the package') + parser.add_argument('-f', '--filename', type=str, default='.conda/meta.yaml', help='Path to meta.yaml, with filename') + args = parser.parse_args() + info = get_info(args.package) + logging.info(f'Information of the last published PyPi package : {info}') + replace_in_file(args.filename, info) diff --git a/.github/has-functional-changes.sh b/.github/has-functional-changes.sh new file mode 100755 index 0000000..48f9780 --- /dev/null +++ b/.github/has-functional-changes.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +IGNORE_DIFF_ON="README.md CONTRIBUTING.md Makefile .gitignore .github/*" + +last_tagged_commit=`git describe --tags --abbrev=0 --first-parent` # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit + +if git diff-index --name-only --exit-code $last_tagged_commit -- . `echo " $IGNORE_DIFF_ON" | sed 's/ / :(exclude)/g'` # Check if any file that has not be listed in IGNORE_DIFF_ON has changed since the last tag was published. +then + echo "No functional changes detected." + exit 1 +else echo "The functional files above were changed." +fi diff --git a/.github/is-version-number-acceptable.sh b/.github/is-version-number-acceptable.sh new file mode 100755 index 0000000..aff071c --- /dev/null +++ b/.github/is-version-number-acceptable.sh @@ -0,0 +1,45 @@ +#! /usr/bin/env bash + +if [[ ${GITHUB_REF#refs/heads/} == master ]] +then + echo "No need for a version check on master." + exit 0 +fi + +if ! $(dirname "$BASH_SOURCE")/has-functional-changes.sh +then + echo "No need for a version update." + exit 0 +fi + +current_version=$(python `dirname "$BASH_SOURCE"`/pyproject_version.py --only_package_version True) # parsing with tomllib is complicated, see https://github.com/python-poetry/poetry/issues/273 +if [ $? -eq 0 ]; then + echo "Package version in pyproject.toml : $current_version" +else + echo "ERROR getting current version: $current_version" + exit 3 +fi + +if [[ -z $current_version ]] +then + echo "Error getting current version" + exit 1 +fi + +if git rev-parse --verify --quiet $current_version +then + echo "Version $current_version already exists in commit:" + git --no-pager log -1 $current_version + echo + echo "Update the version number in pyproject.toml before merging this branch into master." + echo "Look at the CONTRIBUTING.md file to learn how the version number should be updated." + exit 2 +fi + +if ! $(dirname "$BASH_SOURCE")/has-functional-changes.sh | grep --quiet CHANGELOG.md +then + echo "CHANGELOG.md has not been modified, while functional changes were made." + echo "Explain what you changed before merging this branch into master." + echo "Look at the CONTRIBUTING.md file to learn how to write the changelog." + exit 2 +fi diff --git a/.github/lint-changed-python-files.sh b/.github/lint-changed-python-files.sh new file mode 100755 index 0000000..72d8aad --- /dev/null +++ b/.github/lint-changed-python-files.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash + +last_tagged_commit=`git describe --tags --abbrev=0 --first-parent` # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit + +if ! changes=$(git diff-index --name-only --diff-filter=ACMR --exit-code $last_tagged_commit -- "*.py") +then + echo "Linting the following Python files:" + echo $changes + flake8 $changes +else echo "No changed Python files to lint" +fi diff --git a/.github/lint-changed-yaml-tests.sh b/.github/lint-changed-yaml-tests.sh new file mode 100755 index 0000000..16e9943 --- /dev/null +++ b/.github/lint-changed-yaml-tests.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash + +last_tagged_commit=`git describe --tags --abbrev=0 --first-parent` # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit + +if ! changes=$(git diff-index --name-only --diff-filter=ACMR --exit-code $last_tagged_commit -- "tests/*.yaml") +then + echo "Linting the following changed YAML tests:" + echo $changes + yamllint $changes +else echo "No changed YAML tests to lint" +fi diff --git a/.github/publish-git-tag.sh b/.github/publish-git-tag.sh new file mode 100755 index 0000000..f60b980 --- /dev/null +++ b/.github/publish-git-tag.sh @@ -0,0 +1,5 @@ +#! /usr/bin/env bash + +current_version=$(grep '^version =' pyproject.toml | cut -d '"' -f 2) # parsing with tomllib is complicated, see https://github.com/python-poetry/poetry/issues/273 +git tag $current_version +git push --tags # update the repository version diff --git a/.github/pyproject_version.py b/.github/pyproject_version.py new file mode 100644 index 0000000..5da7842 --- /dev/null +++ b/.github/pyproject_version.py @@ -0,0 +1,82 @@ +# Read package version in pyproject.toml and replace it in .conda/recipe.yaml + +import argparse +import logging +import re + +logging.basicConfig(level=logging.INFO, format='%(message)s') +PACKAGE_VERSION = 'X.X.X' +CORE_VERSION = '>=43,<44' +NUMPY_VERSION = '>=1.24.3,<2' + + +def get_versions(): + ''' + Read package version and deps in pyproject.toml + ''' + openfisca_core_api = None + openfisca_tunisia_pension = None + with open('./pyproject.toml', 'r') as file: + content = file.read() + # Extract the version of openfisca_tunisia-pension + version_match = re.search(r'^version\s*=\s*"([\d.]*)"', content, re.MULTILINE) + if version_match: + openfisca_tunisia_pension = version_match.group(1) + else: + raise Exception('Package version not found in pyproject.toml') + # Extract dependencies + version = re.search(r'openfisca-core\[web-api\]\s*(>=\s*[\d\.]*,\s*<\d*)"', content, re.MULTILINE) + if version: + openfisca_core_api = version.group(1) + version = re.search(r'numpy\s*(>=\s*[\d\.]*,\s*<\d*)"', content, re.MULTILINE) + if version: + numpy = version.group(1) + if not openfisca_core_api or not numpy: + raise Exception('Dependencies not found in pyproject.toml') + return { + 'openfisca_tunisia_pension': openfisca_tunisia_pension, + 'openfisca_core_api': openfisca_core_api.replace(' ', ''), + 'numpy': numpy.replace(' ', ''), + } + + +def replace_in_file(filepath: str, info: dict): + ''' + ::filepath:: Path to meta.yaml, with filename + ::info:: Dict with information to populate + ''' + with open(filepath, 'rt') as fin: + meta = fin.read() + # Replace with info from pyproject.toml + if PACKAGE_VERSION not in meta: + raise Exception(f'{PACKAGE_VERSION=} not found in {filepath}') + meta = meta.replace(PACKAGE_VERSION, info['openfisca_tunisia-pension']) + if CORE_VERSION not in meta: + raise Exception(f'{CORE_VERSION=} not found in {filepath}') + meta = meta.replace(CORE_VERSION, info['openfisca_core_api']) + if NUMPY_VERSION not in meta: + raise Exception(f'{NUMPY_VERSION=} not found in {filepath}') + meta = meta.replace(NUMPY_VERSION, info['numpy']) + with open(filepath, 'wt') as fout: + fout.write(meta) + logging.info(f'File {filepath} has been updated with informations from pyproject.toml.') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-r', '--replace', type=bool, default=False, required=False, help='replace in file') + parser.add_argument('-f', '--filename', type=str, default='.conda/recipe.yaml', help='Path to recipe.yaml, with filename') + parser.add_argument('-o', '--only_package_version', type=bool, default=False, help='Only display current package version') + args = parser.parse_args() + info = get_versions() + file = args.filename + if args.only_package_version: + print(f'{info["openfisca_tunisia-pension"]}') # noqa: T201 + exit() + logging.info('Versions :') + print(info) # noqa: T201 + if args.replace: + logging.info(f'Replace in {file}') + replace_in_file(file, info) + else: + logging.info('Dry mode, no replace made') diff --git a/.github/split_tests.py b/.github/split_tests.py new file mode 100644 index 0000000..11e2b3f --- /dev/null +++ b/.github/split_tests.py @@ -0,0 +1,22 @@ +import sys +from glob import glob + + +def split_tests(number_of_files, CI_NODE_TOTAL, CI_NODE_INDEX, test_files_list): + test_files_sublist = [] + + for file_index in range(number_of_files): + file_number = file_index % CI_NODE_TOTAL + if file_number == CI_NODE_INDEX: + test_files_sublist.append(test_files_list[file_index]) + + tests_to_run_string = ' '.join(test_files_sublist) + + return tests_to_run_string + + +if __name__ == '__main__': + CI_NODE_TOTAL, CI_NODE_INDEX = int(sys.argv[1]), int(sys.argv[2]) + test_files_list = glob('tests/**/*.yaml', recursive=True) + glob('tests/**/*.yml', recursive=True) + number_of_files = len(test_files_list) + sys.stdout.write(split_tests(number_of_files, CI_NODE_TOTAL, CI_NODE_INDEX, test_files_list)) diff --git a/.github/test-api.sh b/.github/test-api.sh new file mode 100755 index 0000000..e7101b2 --- /dev/null +++ b/.github/test-api.sh @@ -0,0 +1,14 @@ +#! /usr/bin/env bash + +PORT=5000 +ENDPOINT=spec + +openfisca serve --country-package openfisca_tunisia-pension --port $PORT --workers 1 & +server_pid=$! + +curl --retry-connrefused --retry 10 --retry-delay 5 --fail http://127.0.0.1:$PORT/$ENDPOINT | python -m json.tool > /dev/null +result=$? + +kill $server_pid + +exit $? diff --git a/.github/workflows/tax-benefit.yml b/.github/workflows/tax-benefit.yml new file mode 100644 index 0000000..6bf03cf --- /dev/null +++ b/.github/workflows/tax-benefit.yml @@ -0,0 +1,93 @@ +name: Validate, integrate & deploy to tax-benefit.org + +on: + - push + - workflow_dispatch + +jobs: + validate_yaml: + uses: tax-benefit/actions/.github/workflows/validate_yaml.yml@v2.1.0 + with: + parameters_path: "openfisca_tunisia/parameters" + secrets: + token: ${{ secrets.CONTROL_CENTER_TOKEN }} + + deploy_parameters: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Clone Legislation Parameters Explorer + run: git clone https://git.leximpact.dev/leximpact/legislation-parameters-explorer.git + - name: Install Node.js version LTS + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - name: Install viewer dependencies + run: npm install + working-directory: legislation-parameters-explorer/packages/viewer + - name: Configure viewer + run: | + rm -f .env + cat > .env << EOF + # Customizations to apply to the site (theme, URLs…) + CUSTOMIZATION="openfisca" + + DBNOMICS_DATASET_CODE="openfisca_tunisia" + DBNOMICS_PROVIDER_CODE="OpenFisca" + DBNOMICS_URL="https://db.nomics.world/" + + EDITOR_URL="https://editor.parameters.tn.tax-benefit.org/" + + EXPORT_CSV=true + EXPORT_JSON=false + EXPORT_XLSX=true + + # Path of directory containing legislation parameters of country + PARAMETERS_DIR="../../../openfisca_tunisia/parameters/" + + # Description of parameters remote repository + PARAMETERS_AUTHOR_EMAIL="editor.parameters.tn@tax-benefit.org" + PARAMETERS_AUTHOR_NAME="Éditeur des paramètres d'OpenFisca-Tunisia" + PARAMETERS_BRANCH="main" + PARAMETERS_FORGE_DOMAIN_NAME="github.com" + PARAMETERS_FORGE_TYPE="GitHub" + PARAMETERS_GROUP="openfisca" + PARAMETERS_PROJECT="openfisca-tunisia-pension" + PARAMETERS_PROJECT_DIR="openfisca_tunisia/parameters" + + SHOW_LAST_BREADCRUMB_ITEM = false + + TABLE_OF_CONTENTS_DIR="../../../openfisca_tunisia/tables/" + + TITLE="OpenFisca-Tunisia - الجباية المفتوحة تونس" + + # Path of file containing units used by French legislation parameters + UNITS_FILE_PATH="../../../openfisca_tunisia/units.yaml" + EOF + working-directory: legislation-parameters-explorer/packages/viewer + - name: Initialize .svelte-kit directory of viewer + run: npx svelte-kit sync + working-directory: legislation-parameters-explorer/packages/viewer + - name: Generate data of viewer + run: npx tsx src/scripts/generate_data.ts + working-directory: legislation-parameters-explorer/packages/viewer + - name: Build viewer + run: npm run build + working-directory: legislation-parameters-explorer/packages/viewer + - name: Configure ssh for deployment to server + uses: tanmancan/action-setup-ssh-agent-key@1.0.0 + with: + ssh-auth-sock: /tmp/my_auth.sock + ssh-private-key: ${{ secrets.PARAMETERS_EXPLORER_SSH_PRIVATE_KEY }} + ssh-public-key: ${{ secrets.PARAMETERS_EXPLORER_SSH_KNOWN_HOSTS }} + - name: Deploy to Server using rsync + run: rsync -az --delete -e "ssh -J ssh-proxy@parameters.tn.tax-benefit.org:2222" build/ parameters.tn.tax-benefit.org@10.131.0.2:public_html/ + working-directory: legislation-parameters-explorer/packages/viewer + + deploy_simulator: + uses: tax-benefit/actions/.github/workflows/deploy.yml@v2.1.0 + with: + python_package: "openfisca_tunisia" + secrets: + token: ${{ secrets.CONTROL_CENTER_TOKEN }} diff --git a/.github/workflows/validate_yaml.yml b/.github/workflows/validate_yaml.yml new file mode 100644 index 0000000..ac97c47 --- /dev/null +++ b/.github/workflows/validate_yaml.yml @@ -0,0 +1,15 @@ +name: Validate YAML + +on: + push: + workflow_dispatch: + pull_request: + types: [opened, reopened] + +jobs: + validate_yaml: + uses: tax-benefit/actions/.github/workflows/validate_yaml.yml@v1 + with: + parameters_path: "openfisca_tunisia/parameters" + secrets: + token: ${{ secrets.CONTROL_CENTER_TOKEN }} diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..1afdb9e --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,236 @@ +name: OpenFisca Tunisia + +on: + push: + pull_request: + types: [opened, reopened] + +env: + DEFAULT_PYTHON_VERSION: '3.10.6' + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: ["ubuntu-20.04"] # On peut ajouter "macos-latest" si besoin + python-version: ["3.9.9", "3.10.6"] + openfisca-dependencies: [minimal, maximal] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-${{ matrix.os }}-${{ matrix.openfisca-dependencies }} + restore-keys: | # in case of a cache miss (systematically unless the same commit is built repeatedly), the keys below will be used to restore dependencies from previous builds, and the cache will be stored at the end of the job, making up-to-date dependencies available for all jobs of the workflow; see more at https://docs.github.com/en/actions/advanced-guides/caching-dependencies-to-speed-up-workflows#example-using-the-cache-action + build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ matrix.os }} + build-${{ env.pythonLocation }}-${{ matrix.os }} + - name: Build package + run: make build + - name: Minimal version + if: matrix.openfisca-dependencies == 'minimal' + run: | # Installs the OpenFisca dependencies minimal version from pyproject.toml + pip install $(python ${GITHUB_WORKSPACE}/.github/get_minimal_version.py) + - name: Cache release + id: restore-release + uses: actions/cache@v4 + with: + path: dist + key: release-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-${{ matrix.os }}-${{ matrix.openfisca-dependencies }} + + lint-files: + runs-on: ubuntu-20.04 + strategy: + matrix: + dependencies-version: [maximal] + needs: [ build ] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all the tags + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-ubuntu-20.04-${{ matrix.dependencies-version }} + - run: make check-syntax-errors + - run: make check-style + - name: Lint Python files + run: "${GITHUB_WORKSPACE}/.github/lint-changed-python-files.sh" + - name: Lint YAML tests + run: "${GITHUB_WORKSPACE}/.github/lint-changed-yaml-tests.sh" + + test-python: + runs-on: ${{ matrix.os }} + needs: [ build ] + strategy: + fail-fast: true + matrix: + os: [ "ubuntu-20.04" ] # On peut ajouter "macos-latest" si besoin + python-version: ["3.9.9", "3.10.6"] + openfisca-dependencies: [minimal, maximal] + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-${{ matrix.os }}-${{ matrix.openfisca-dependencies }} + - run: | + shopt -s globstar + openfisca test --country-package openfisca_tunisia tests/**/*.py + if: matrix.openfisca-dependencies != 'minimal' || matrix.python-version != '3.9.9' + + test-path-length: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Test max path length + run: make check-path-length + + test-yaml: + runs-on: ubuntu-20.04 + needs: [ build ] + strategy: + fail-fast: false + matrix: + # Set N number of parallel jobs to run tests on. Here we use 10 jobs + # Remember to update ci_node_index below to 0..N-1 + ci_node_total: [ 10 ] + ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + openfisca-dependencies: [minimal, maximal] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-ubuntu-20.04-${{ matrix.openfisca-dependencies }} + - name: Split YAML tests + id: yaml-test + env: + CI_NODE_TOTAL: ${{ matrix.ci_node_total }} + CI_NODE_INDEX: ${{ matrix.ci_node_index }} + run: | + echo "TEST_FILES_SUBLIST=$(python "${GITHUB_WORKSPACE}/.github/split_tests.py" ${CI_NODE_TOTAL} ${CI_NODE_INDEX})" >> $GITHUB_ENV + - name: Run YAML test + run: | + openfisca test --country-package openfisca_tunisia ${TEST_FILES_SUBLIST} + + test-api: + runs-on: ubuntu-20.04 + strategy: + matrix: + dependencies-version: [maximal] + needs: [ build ] + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-ubuntu-20.04-${{ matrix.dependencies-version }} + - name: Test the Web API + run: "${GITHUB_WORKSPACE}/.github/test-api.sh" + + check-version-and-changelog: + runs-on: ubuntu-20.04 + needs: [ lint-files, test-python, test-yaml, test-api ] # Last job to run + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all the tags + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Check version number has been properly updated + run: "${GITHUB_WORKSPACE}/.github/is-version-number-acceptable.sh" + + # GitHub Actions does not have a halt job option, to stop from deploying if no functional changes were found. + # We build a separate job to substitute the halt option. + # The `deploy` job is dependent on the output of the `check-for-functional-changes` job. + check-for-functional-changes: + runs-on: ubuntu-20.04 + if: github.ref == 'refs/heads/master' # Only triggered for the `master` or `main` branch + needs: [ check-version-and-changelog ] + outputs: + status: ${{ steps.stop-early.outputs.status }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all the tags + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - id: stop-early + run: if "${GITHUB_WORKSPACE}/.github/has-functional-changes.sh" ; then echo "::set-output name=status::success" ; fi + + deploy: + runs-on: ubuntu-20.04 + strategy: + matrix: + dependencies-version: [maximal] + needs: [ check-for-functional-changes ] + if: needs.check-for-functional-changes.outputs.status == 'success' + env: + PYPI_USERNAME: __token__ + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all the tags + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Cache build + id: restore-build + uses: actions/cache@v4 + with: + path: ${{ env.pythonLocation }} + key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-ubuntu-20.04-${{ matrix.dependencies-version }} + - name: Cache release + id: restore-release + uses: actions/cache@v4 + with: + path: dist + key: release-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-ubuntu-20.04-${{ matrix.dependencies-version }} + - name: Upload a Python package to PyPi + run: twine upload dist/* --username $PYPI_USERNAME --password $PYPI_TOKEN + - name: Publish a git tag + run: "${GITHUB_WORKSPACE}/.github/publish-git-tag.sh" diff --git a/README.md b/README.md index 86f06d1..0e7aeff 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ OpenFisca est un projet de logiciel libre. Son code source est distribué sous la licence [GNU Affero General Public Licence](http://www.gnu.org/licenses/agpl.html) -version 3 ou ultérieure (cf. [LICENSE](https://github.com/openfisca/openfisca-tunisia/blob/master/LICENSE)). +version 3 ou ultérieure (cf. [LICENSE](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/LICENSE)). N'hésitez pas à rejoindre l'équipe de développement OpenFisca ! Pour en savoir plus, une [documentation](https://doc.openfisca.fr/contribute/index.html) est à votre disposition. @@ -24,7 +24,7 @@ N'hésitez pas à rejoindre l'équipe de développement OpenFisca ! Pour en savo

تم توزيع مصدر هذا البرنامج تحت رخصة أفيرو العامة الثالثة أو ما أعلى

-

تعالو انضمو إلى فريق الجباية المفتوحة و ساهمو في تطوير البرنامج! +

تعالو انضمو إلى فريق الجباية المفتوحة و ساهمو في تطوير البرنامج! انظرو للموقع الرسمي للمزيد من المعلومات

@@ -32,7 +32,7 @@ N'hésitez pas à rejoindre l'équipe de développement OpenFisca ! Pour en savo OpenFisca is a free software project. Its source code is distributed under the [GNU Affero General Public Licence](http://www.gnu.org/licenses/agpl.html) -version 3 or later (see [LICENSE](https://github.com/openfisca/openfisca-tunisia/blob/master/LICENSE) file). +version 3 or later (see [LICENSE](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/LICENSE) file). Feel free to join the OpenFisca development team! See the [documentation](https://doc.openfisca.fr/contribute/index.html) for more information. @@ -76,7 +76,7 @@ Ensuite, afin de créer un environnement de travail propre et pour vous permettr sudo pip install pew ``` -Il vous est désormais possible de créer votre premier environnement dédié à OpenFisca-Tunisia Pension. +Il vous est désormais possible de créer votre premier environnement dédié à OpenFisca-Tunisia Pension. ### Création d'environnement virtuel @@ -108,7 +108,7 @@ cd openfisca-tunisia-pension pip install -e . ``` -:tada: Félicitations, vous avez désormais terminé l'installation d'OpenFisca Tunisia Pension ! +:tada: Félicitations, vous avez désormais terminé l'installation d'OpenFisca Tunisia Pension ! Vous pouvez vérifier que votre environnement fonctionne bien en démarrant les tests tel que décrit dans le paragraphe suivant. @@ -144,7 +144,7 @@ Le format d'un test yaml est décrit dans la [documentation officielle](https:// Ainsi, si vous souhaitez exécuter le test yaml `openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml`, utilisez la commande : ``` -openfisca-run-test -c openfisca_tunisia_pension openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml +openfisca-run-test -c openfisca_tunisia_pension openfisca_tunisia_pension/tests/formulas/pension_rsna.yaml ``` ### Tout tester diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bb9efa5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +[project] +name = "OpenFisca-Tunisia-Pension" +version = "0.38.0" +description = "OpenFisca Rules as Code model for Tunisia pensions." +readme = "README.md" +keywords = ["microsimulation", "tax", "benefit", "pension", "rac", "rules-as-code", "tunisia"] +authors = [ + {name = "OpenFisca Team", email = "contact@openfisca.org"}, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Information Analysis", +] +requires-python = ">= 3.9" +dependencies = [ + "numpy >=1.24.3, <2", + "openfisca-core[web-api] >=43, <44" +] + +[project.urls] +Homepage = "https://github.com/openfisca/openfisca-tunisia-pension" +Repository = "https://github.com/openfisca/openfisca-tunisia-pension" +Documentation = "https://openfisca.org/doc" +Issues = "https://github.com/openfisca/openfisca-tunisia-pension/issues" +Changelog = "https://github.com/openfisca/openfisca-tunisia-pension/blob/main/CHANGELOG.md" + +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project.optional-dependencies] +dev = [ + "autopep8 >=2.0.2, <3.0", + "Flake8-pyproject>=1.2.3, <2.0.0", # To read flake8 configuration from pyproject.toml + "flake8 >=6.0.0, <7.0.0", + "flake8-print >=5.0.0, <6.0.0", + "flake8-quotes >=3.3.2", + "pytest", # Let OpenFisca-Core decide pytest version + "scipy >=1.10.1, <2.0", + "requests >=2.28.2, <3.0", + "yamllint >=1.30.0, <2.0" +] +notebook = [ + 'ipykernel >= 4.8', + 'jupyter-client >= 5.2', + 'matplotlib >= 2.2', + 'nbconvert >= 5.3', + 'nbformat >= 4.4', + 'pandas >= 0.22.0', +] + +[tool.flake8] +# ; E128/133: We prefer hang-closing visual indents +# ; E251: We prefer `function(x = 1)` over `function(x=1)` +# ; E501: We do not enforce a maximum line length +# ; F403/405: We ignore * imports +# ; W503/504: We break lines before binary operators (Knuth's style) +hang-closing = true +ignore = ["E128","E251","F403","F405","E501","W503"] +docstring-quotes = "single" +inline-quotes = "single" +multiline-quotes = "single" + +[tool.pep8] +hang-closing = true +ignore = ["E128","E251","F403","F405","E501","W503"] +in-place = true + +[tool.pytest.ini_options] +addopts = "--showlocals --exitfirst --doctest-modules --disable-pytest-warnings" +testpaths = "tests" +python_files = "**/*.py" +filterwarnings = [ + "error", + "ignore::UserWarning", + 'ignore:function ham\(\) is deprecated:DeprecationWarning', + "ignore:invalid value encountered in divide:RuntimeWarning", + "ignore:invalid value encountered in multiply:RuntimeWarning", + "ignore:divide by zero encountered in divide:RuntimeWarning", +] From 322b72660851262dd16e3bccb791a81ba5f6093a Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:27:44 +0100 Subject: [PATCH 07/12] Switch to pyproject.toml --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- pyproject.toml | 8 +++-- setup.py | 52 -------------------------------- 3 files changed, 6 insertions(+), 56 deletions(-) delete mode 100755 setup.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e73a542..5128457 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,7 +24,7 @@ Quelques conseils à prendre en compte : - [ ] Regardez s'il n'y a pas une [proposition introduisant ces mêmes changements](https://github.com/openfisca/openfisca-tunisia-pension/pulls). - [ ] Documentez votre contribution avec des références législatives. - [ ] Mettez à jour ou ajoutez des tests correspondant à votre contribution. -- [ ] Augmentez le [numéro de version](https://speakerdeck.com/mattisg/git-session-2-strategies?slide=81) dans [`setup.py`](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/setup.py). +- [ ] Augmentez le [numéro de version](https://speakerdeck.com/mattisg/git-session-2-strategies?slide=81) dans [`pyproject.toml`](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/pyproject.toml). - [ ] Mettez à jour le [`CHANGELOG.md`](https://github.com/openfisca/openfisca-tunisia-pension/blob/master/CHANGELOG.md). - [ ] Assurez-vous de bien décrire votre contribution, comme indiqué ci-dessus diff --git a/pyproject.toml b/pyproject.toml index bb9efa5..641d611 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "OpenFisca-Tunisia-Pension" -version = "0.38.0" +version = "2.0.0" description = "OpenFisca Rules as Code model for Tunisia pensions." readme = "README.md" keywords = ["microsimulation", "tax", "benefit", "pension", "rac", "rules-as-code", "tunisia"] @@ -19,8 +19,10 @@ classifiers = [ ] requires-python = ">= 3.9" dependencies = [ - "numpy >=1.24.3, <2", - "openfisca-core[web-api] >=43, <44" + 'bottleneck >=1.3.2,<=2.0.0', + 'numpy >=1.24.3, <2', + 'openfisca-core[web-api] >=43, <44', + 'scipy >= 0.12', ] [project.urls] diff --git a/setup.py b/setup.py deleted file mode 100755 index 57811d8..0000000 --- a/setup.py +++ /dev/null @@ -1,52 +0,0 @@ -#! /usr/bin/env python - - -'''Tunisia Pension specific model for OpenFisca -- a versatile microsimulation free software''' - - -from setuptools import setup, find_packages - - -classifiers = '''\ -Development Status :: 2 - Pre-Alpha -License :: OSI Approved :: GNU Affero General Public License v3 -Operating System :: POSIX -Programming Language :: Python -Topic :: Scientific/Engineering :: Information Analysis -''' - -doc_lines = __doc__.split('\n') - - -setup( - name = 'OpenFisca-Tunisia-Pension', - version = '2.0.0', - author = 'OpenFisca Team', - author_email = 'contact@openfisca.org', - classifiers = [classifier for classifier in classifiers.split('\n') if classifier], - description = doc_lines[0], - keywords = 'benefit microsimulation pension social tax tunisia', - license = 'http://www.fsf.org/licensing/licenses/agpl-3.0.html', - long_description = '\n'.join(doc_lines[2:]), - url = 'https://github.com/openfisca/openfisca-tunisia-pension', - - data_files = [], - extras_require = { - 'dev': [ - 'autopep8 >=2.0.2, <3.0', - 'flake8 >=6.0.0, <7.0.0', - 'flake8-print >=5.0.0, <6.0.0', - 'flake8-quotes >=3.3.2', - 'pytest >=7.2.2, <8.0', - 'scipy >=1.10.1, <2.0', # Only used to test de_net_a_brut reform - 'requests >=2.28.2, <3.0', - 'yamllint >=1.30.0, <2.0' - ], - }, - install_requires = [ - 'bottleneck >=1.3.2,<=2.0.0', - 'OpenFisca-Core >= 41.4.1, < 42.0', - 'scipy >= 0.12', - ], - packages = find_packages(exclude=['openfisca_tunisia_pension.tests*']), - ) From a9367ab7951451ddcf0384366a9dcb2ba794a379 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:30:30 +0100 Subject: [PATCH 08/12] Fix test call --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1afdb9e..bc7f483 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -96,7 +96,7 @@ jobs: key: build-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-${{ github.sha }}-${{ matrix.os }}-${{ matrix.openfisca-dependencies }} - run: | shopt -s globstar - openfisca test --country-package openfisca_tunisia tests/**/*.py + openfisca test --country-package openfisca_tunisia_pension tests/**/*.py if: matrix.openfisca-dependencies != 'minimal' || matrix.python-version != '3.9.9' test-path-length: @@ -143,7 +143,7 @@ jobs: echo "TEST_FILES_SUBLIST=$(python "${GITHUB_WORKSPACE}/.github/split_tests.py" ${CI_NODE_TOTAL} ${CI_NODE_INDEX})" >> $GITHUB_ENV - name: Run YAML test run: | - openfisca test --country-package openfisca_tunisia ${TEST_FILES_SUBLIST} + openfisca test --country-package openfisca_tunisia_pension ${TEST_FILES_SUBLIST} test-api: runs-on: ubuntu-20.04 From c197c071c9ac7b7aff07c6c8920d42dd9df39e3d Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:39:15 +0100 Subject: [PATCH 09/12] Move away tests from package --- .../model => .github/tests}/__init__.py | 0 .github/tests/base.py | 10 ++++ .github/tests/formulas/pension_rsna.yaml | 50 +++++++++++++++++++ .../formulas/salaire_reference_rsna.yaml | 49 ++++++++++++++++++ .../tunisia_pension_taxbenefitsystem.py | 2 +- .../variables/__init__.py | 0 .../{model => variables}/data.py | 0 .../{model => variables}/pension.py | 0 8 files changed, 110 insertions(+), 1 deletion(-) rename {openfisca_tunisia_pension/model => .github/tests}/__init__.py (100%) create mode 100644 .github/tests/base.py create mode 100644 .github/tests/formulas/pension_rsna.yaml create mode 100644 .github/tests/formulas/salaire_reference_rsna.yaml create mode 100644 openfisca_tunisia_pension/variables/__init__.py rename openfisca_tunisia_pension/{model => variables}/data.py (100%) rename openfisca_tunisia_pension/{model => variables}/pension.py (100%) diff --git a/openfisca_tunisia_pension/model/__init__.py b/.github/tests/__init__.py similarity index 100% rename from openfisca_tunisia_pension/model/__init__.py rename to .github/tests/__init__.py diff --git a/.github/tests/base.py b/.github/tests/base.py new file mode 100644 index 0000000..dd13316 --- /dev/null +++ b/.github/tests/base.py @@ -0,0 +1,10 @@ +from openfisca_tunisia_pension import TunisiaPensionTaxBenefitSystem + + +__all__ = [ + 'tax_benefit_system', + 'TunisiaPensionTaxBenefitSystem', + ] + + +tax_benefit_system = TunisiaPensionTaxBenefitSystem() diff --git a/.github/tests/formulas/pension_rsna.yaml b/.github/tests/formulas/pension_rsna.yaml new file mode 100644 index 0000000..543aaf5 --- /dev/null +++ b/.github/tests/formulas/pension_rsna.yaml @@ -0,0 +1,50 @@ +- name: "Individu salarié 12000 DT par an durant toute sa carrière" + period: 2011 + absolute_error_margin: 0.5 + input: + age: 60 + trimestres_valides: 50 + salaire: + 1975: 12000 + 1976: 12000 + 1977: 12000 + 1978: 12000 + 1979: 12000 + 1980: 12000 + 1981: 12000 + 1982: 12000 + 1983: 12000 + 1984: 12000 + 1985: 12000 + 1986: 12000 + 1987: 12000 + 1988: 12000 + 1989: 12000 + 1990: 12000 + 1991: 12000 + 1992: 12000 + 1993: 12000 + 1994: 12000 + 1995: 12000 + 1996: 12000 + 1997: 12000 + 1998: 12000 + 1999: 12000 + 2000: 12000 + 2001: 12000 + 2002: 12000 + 2003: 12000 + 2004: 12000 + 2005: 12000 + 2006: 12000 + 2007: 12000 + 2008: 12000 + 2009: 12000 + 2010: 12000 + 2011: 12000 + 2012: 12000 + 2013: 12000 + 2014: 12000 + output: + salaire_reference_rsna: 12000 + pension_rsna: 5400 diff --git a/.github/tests/formulas/salaire_reference_rsna.yaml b/.github/tests/formulas/salaire_reference_rsna.yaml new file mode 100644 index 0000000..a763fd9 --- /dev/null +++ b/.github/tests/formulas/salaire_reference_rsna.yaml @@ -0,0 +1,49 @@ +- name: "Individu salarié 12000 DT par an toute sa carrière" + period: 2011 + absolute_error_margin: 0.5 + input: + age: 60 + trimestres_valides: 50 + salaire: + 1975: 12000 + 1976: 12000 + 1977: 12000 + 1978: 12000 + 1979: 12000 + 1980: 12000 + 1981: 12000 + 1982: 12000 + 1983: 12000 + 1984: 12000 + 1985: 12000 + 1986: 12000 + 1987: 12000 + 1988: 12000 + 1989: 12000 + 1990: 12000 + 1991: 12000 + 1992: 12000 + 1993: 12000 + 1994: 12000 + 1995: 12000 + 1996: 12000 + 1997: 12000 + 1998: 12000 + 1999: 12000 + 2000: 12000 + 2001: 12000 + 2002: 12000 + 2003: 12000 + 2004: 12000 + 2005: 12000 + 2006: 12000 + 2007: 12000 + 2008: 12000 + 2009: 12000 + 2010: 12000 + 2011: 12000 + 2012: 12000 + 2013: 12000 + 2014: 12000 + output: + salaire_reference_rsna: 12000 diff --git a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py index a45d20e..a844e21 100644 --- a/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py +++ b/openfisca_tunisia_pension/tunisia_pension_taxbenefitsystem.py @@ -18,7 +18,7 @@ def __init__(self): super(TunisiaPensionTaxBenefitSystem, self).__init__(entities.entities) # We add to our tax and benefit system all the variables - self.add_variables_from_directory(os.path.join(COUNTRY_DIR, 'model')) + self.add_variables_from_directory(os.path.join(COUNTRY_DIR, 'variables')) # We add to our tax and benefit system all the legislation parameters defined in the parameters files parameters_path = os.path.join(COUNTRY_DIR, 'parameters') diff --git a/openfisca_tunisia_pension/variables/__init__.py b/openfisca_tunisia_pension/variables/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openfisca_tunisia_pension/model/data.py b/openfisca_tunisia_pension/variables/data.py similarity index 100% rename from openfisca_tunisia_pension/model/data.py rename to openfisca_tunisia_pension/variables/data.py diff --git a/openfisca_tunisia_pension/model/pension.py b/openfisca_tunisia_pension/variables/pension.py similarity index 100% rename from openfisca_tunisia_pension/model/pension.py rename to openfisca_tunisia_pension/variables/pension.py From f1801056c1e9c50c234dec9dbc8e0bd012961dd8 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:40:04 +0100 Subject: [PATCH 10/12] Remove unused file --- openfisca_tunisia_pension/tests/base.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 openfisca_tunisia_pension/tests/base.py diff --git a/openfisca_tunisia_pension/tests/base.py b/openfisca_tunisia_pension/tests/base.py deleted file mode 100644 index dd13316..0000000 --- a/openfisca_tunisia_pension/tests/base.py +++ /dev/null @@ -1,10 +0,0 @@ -from openfisca_tunisia_pension import TunisiaPensionTaxBenefitSystem - - -__all__ = [ - 'tax_benefit_system', - 'TunisiaPensionTaxBenefitSystem', - ] - - -tax_benefit_system = TunisiaPensionTaxBenefitSystem() From 8b35d06c859239c28bdeef2d7c1f7550fa01ef99 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:46:50 +0100 Subject: [PATCH 11/12] Remove unused file --- .github/tests/base.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .github/tests/base.py diff --git a/.github/tests/base.py b/.github/tests/base.py deleted file mode 100644 index dd13316..0000000 --- a/.github/tests/base.py +++ /dev/null @@ -1,10 +0,0 @@ -from openfisca_tunisia_pension import TunisiaPensionTaxBenefitSystem - - -__all__ = [ - 'tax_benefit_system', - 'TunisiaPensionTaxBenefitSystem', - ] - - -tax_benefit_system = TunisiaPensionTaxBenefitSystem() From 3e5d63e07d80bd37618b8ae9c906922584b00e46 Mon Sep 17 00:00:00 2001 From: Mahdi Ben Jelloul Date: Thu, 14 Nov 2024 12:47:31 +0100 Subject: [PATCH 12/12] Move tests --- {.github/tests => tests}/__init__.py | 0 {.github/tests => tests}/formulas/pension_rsna.yaml | 0 {.github/tests => tests}/formulas/salaire_reference_rsna.yaml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {.github/tests => tests}/__init__.py (100%) rename {.github/tests => tests}/formulas/pension_rsna.yaml (100%) rename {.github/tests => tests}/formulas/salaire_reference_rsna.yaml (100%) diff --git a/.github/tests/__init__.py b/tests/__init__.py similarity index 100% rename from .github/tests/__init__.py rename to tests/__init__.py diff --git a/.github/tests/formulas/pension_rsna.yaml b/tests/formulas/pension_rsna.yaml similarity index 100% rename from .github/tests/formulas/pension_rsna.yaml rename to tests/formulas/pension_rsna.yaml diff --git a/.github/tests/formulas/salaire_reference_rsna.yaml b/tests/formulas/salaire_reference_rsna.yaml similarity index 100% rename from .github/tests/formulas/salaire_reference_rsna.yaml rename to tests/formulas/salaire_reference_rsna.yaml