openfisca-excel-reform 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openfisca_excel_reform-0.0.1/PKG-INFO +12 -0
- openfisca_excel_reform-0.0.1/README.md +3 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform/__init__.py +235 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform.egg-info/PKG-INFO +12 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform.egg-info/SOURCES.txt +9 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform.egg-info/dependency_links.txt +1 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform.egg-info/requires.txt +2 -0
- openfisca_excel_reform-0.0.1/openfisca_excel_reform.egg-info/top_level.txt +1 -0
- openfisca_excel_reform-0.0.1/pyproject.toml +20 -0
- openfisca_excel_reform-0.0.1/setup.cfg +4 -0
- openfisca_excel_reform-0.0.1/tests/test_reform.py +308 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openfisca-excel-reform
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
8
|
+
Requires-Dist: openfisca-core[web-api]>=43
|
|
9
|
+
|
|
10
|
+
Excel reform provide a way to modify a subtree of parameters with an Excel file.
|
|
11
|
+
|
|
12
|
+
`TestExcelReformIntegration.test_build_reform_excel_and_generate_template` illustrates how this package can be used.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import IO, Iterator
|
|
4
|
+
|
|
5
|
+
from openfisca_core.types import TaxBenefitSystem
|
|
6
|
+
|
|
7
|
+
from datetime import date
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import openpyxl
|
|
11
|
+
|
|
12
|
+
from openfisca_core.parameters.parameter_node import ParameterNode
|
|
13
|
+
from openfisca_core.parameters.parameter_scale import ParameterScale
|
|
14
|
+
from openfisca_core.parameters.parameter_scale_bracket import ParameterScaleBracket
|
|
15
|
+
from openfisca_core.reforms import Reform
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_parameter_node(
|
|
19
|
+
p: ParameterNode, segments: str | list[str]
|
|
20
|
+
) -> tuple[ParameterNode | ParameterScale | ParameterScaleBracket, list[str]]:
|
|
21
|
+
if isinstance(segments, str):
|
|
22
|
+
segments = segments.split(".")
|
|
23
|
+
if segments:
|
|
24
|
+
if hasattr(p, "children"):
|
|
25
|
+
return get_parameter_node(p.children[segments[0]], segments[1:])
|
|
26
|
+
else:
|
|
27
|
+
return p, segments
|
|
28
|
+
else:
|
|
29
|
+
return p, []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExcelReformBuilder:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
baseline: type[TaxBenefitSystem],
|
|
36
|
+
path_or_file: Path | str | IO[bytes],
|
|
37
|
+
) -> None:
|
|
38
|
+
# data_only = True to get the value stored the last time Excel read the sheet
|
|
39
|
+
self.wb = openpyxl.load_workbook(path_or_file, data_only=True)
|
|
40
|
+
self.baseline = baseline
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def suffixes(self) -> list[str]:
|
|
44
|
+
return [
|
|
45
|
+
sheetname[len("Paramètres") :]
|
|
46
|
+
for sheetname in self.wb.sheetnames
|
|
47
|
+
if sheetname.startswith("Paramètres")
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def reforms(self) -> Iterator[ExcelReform]:
|
|
52
|
+
for suffix in self.suffixes:
|
|
53
|
+
yield self.build_reform(suffix)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def root_name(self) -> str:
|
|
57
|
+
assert self.wb["Config"]["A2"].value == "root"
|
|
58
|
+
return self.wb["Config"]["B2"].value
|
|
59
|
+
|
|
60
|
+
def get_parameters(self, suffix: str) -> list[tuple[str, float, date]]:
|
|
61
|
+
sheet = self.wb[f"Paramètres{suffix}"]
|
|
62
|
+
nom_col = [
|
|
63
|
+
i for i, cell in enumerate(sheet.iter_cols()) if cell[0].value == "Nom"
|
|
64
|
+
]
|
|
65
|
+
valeur_col = [
|
|
66
|
+
i for i, cell in enumerate(sheet.iter_cols()) if cell[0].value == "Valeur"
|
|
67
|
+
]
|
|
68
|
+
date_col = [
|
|
69
|
+
i for i, cell in enumerate(sheet.iter_cols()) if cell[0].value == "Date"
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
assert nom_col, "La colonne 'Nom' est manquante"
|
|
73
|
+
assert valeur_col is not None, "La colonne 'Valeur' est manquante"
|
|
74
|
+
assert date_col, "La colonne 'Date' est manquante"
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
(
|
|
78
|
+
row[nom_col[0]],
|
|
79
|
+
row[valeur_col[0]],
|
|
80
|
+
row[date_col[0]].date(),
|
|
81
|
+
)
|
|
82
|
+
for row in sheet.iter_rows(min_row=2, values_only=True)
|
|
83
|
+
if row[date_col[0]]
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
def build_reform(self, suffix: str) -> "ExcelReform":
|
|
87
|
+
return ExcelReform(
|
|
88
|
+
suffix,
|
|
89
|
+
self.baseline,
|
|
90
|
+
self.root_name,
|
|
91
|
+
self.get_parameters(suffix),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ExcelReformTemplateGenerator:
|
|
96
|
+
def __init__(
|
|
97
|
+
self, baseline: type[TaxBenefitSystem], root_name: str, period: date
|
|
98
|
+
) -> None:
|
|
99
|
+
self.baseline = baseline
|
|
100
|
+
self.root_name = root_name
|
|
101
|
+
self.period = period
|
|
102
|
+
|
|
103
|
+
def generate_parameter_tree_values(self, parameter) -> list[tuple[str, float]]:
|
|
104
|
+
values = []
|
|
105
|
+
if type(parameter) is ParameterNode:
|
|
106
|
+
for child in parameter.children.values():
|
|
107
|
+
values.extend(self.generate_parameter_tree_values(child))
|
|
108
|
+
else:
|
|
109
|
+
value = parameter.get_at_instant(self.period)
|
|
110
|
+
name = parameter.name.removeprefix(self.root_name + ".")
|
|
111
|
+
if type(parameter) is ParameterScale:
|
|
112
|
+
threshold_values = (
|
|
113
|
+
value.amounts
|
|
114
|
+
if parameter.metadata.get("type") == "single_amount"
|
|
115
|
+
else value.rates
|
|
116
|
+
)
|
|
117
|
+
for threshold, val in zip(value.thresholds, threshold_values):
|
|
118
|
+
values.append((f"{name}.{threshold}", val))
|
|
119
|
+
else:
|
|
120
|
+
if isinstance(value, list):
|
|
121
|
+
values.append((name, ", ".join(value)))
|
|
122
|
+
else:
|
|
123
|
+
values.append((name, value))
|
|
124
|
+
return sorted(values, key=lambda v: v[0])
|
|
125
|
+
|
|
126
|
+
def parameter_data(self) -> list[tuple[str, float]]:
|
|
127
|
+
root_parameter, _ = get_parameter_node(self.baseline.parameters, self.root_name)
|
|
128
|
+
return self.generate_parameter_tree_values(root_parameter)
|
|
129
|
+
|
|
130
|
+
def save_template_xlsx(
|
|
131
|
+
self,
|
|
132
|
+
path_or_file: Path | str | IO[bytes],
|
|
133
|
+
) -> None:
|
|
134
|
+
wb = openpyxl.Workbook()
|
|
135
|
+
ws_config = wb.active
|
|
136
|
+
ws_config.title = "Config"
|
|
137
|
+
ws_config.append(["Parameter name", "Parameter value"])
|
|
138
|
+
ws_config.append(["root", self.root_name])
|
|
139
|
+
|
|
140
|
+
wb.create_sheet(title="Paramètres")
|
|
141
|
+
ws_params = wb["Paramètres"]
|
|
142
|
+
ws_params.append(["Nom", "Date", "Valeur"])
|
|
143
|
+
|
|
144
|
+
for name, value in self.parameter_data():
|
|
145
|
+
ws_params.append([name, self.period, value])
|
|
146
|
+
|
|
147
|
+
# Heuristics to adjust to content
|
|
148
|
+
ws_params.column_dimensions["A"].width = 70
|
|
149
|
+
ws_params.column_dimensions["B"].width = 12
|
|
150
|
+
ws_params.column_dimensions["C"].width = 12
|
|
151
|
+
wb.active = ws_params
|
|
152
|
+
|
|
153
|
+
wb.save(path_or_file)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class ExcelReform(Reform):
|
|
157
|
+
"""A class representing a reform defined in an Excel file.
|
|
158
|
+
|
|
159
|
+
This class extends the Reform class to provide functionality specific to reforms defined in Excel format.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
name: str,
|
|
165
|
+
baseline: TaxBenefitSystem,
|
|
166
|
+
root_name: str,
|
|
167
|
+
reformed_parameters: list[tuple[str, str, date | None]] | None = None,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Initialize the ExcelReform instance.
|
|
170
|
+
|
|
171
|
+
:param baseline: Baseline TaxBenefitSystem.
|
|
172
|
+
:param path: Path to the Excel file defining the reform.
|
|
173
|
+
:param suffix: Suffix to identify the relevant parameters sheet.
|
|
174
|
+
"""
|
|
175
|
+
self.name = name
|
|
176
|
+
self.root_name = root_name
|
|
177
|
+
self.reformed_parameters = reformed_parameters or []
|
|
178
|
+
super().__init__(baseline)
|
|
179
|
+
|
|
180
|
+
def apply(self):
|
|
181
|
+
def modify_parameters(local_parameters: ParameterNode) -> ParameterNode:
|
|
182
|
+
# Go down the parameter tree to the parameter root of this Excel reform
|
|
183
|
+
root, _ = get_parameter_node(local_parameters, self.root_name)
|
|
184
|
+
# Dict to track scale-based multiline parameters
|
|
185
|
+
params_with_thresholds: dict[
|
|
186
|
+
str, tuple[ParameterScale, list[tuple[float, ParameterScaleBracket]]]
|
|
187
|
+
] = dict()
|
|
188
|
+
|
|
189
|
+
for name, value, date_ in self.reformed_parameters:
|
|
190
|
+
leaf, threshold = get_parameter_node(root, name)
|
|
191
|
+
if type(leaf) is ParameterScale:
|
|
192
|
+
threshold_value = float(".".join(threshold))
|
|
193
|
+
prop_name = (
|
|
194
|
+
"amount"
|
|
195
|
+
if leaf.metadata.get("type") == "single_amount"
|
|
196
|
+
else "rate"
|
|
197
|
+
)
|
|
198
|
+
stripped_name = ".".join(name.split(".")[:-1])
|
|
199
|
+
|
|
200
|
+
bracket = ParameterScaleBracket(
|
|
201
|
+
data={
|
|
202
|
+
"threshold": {
|
|
203
|
+
date_.isoformat(): {"value": threshold_value}
|
|
204
|
+
},
|
|
205
|
+
prop_name: {date_.isoformat(): {"value": value}},
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
params_with_thresholds.setdefault(stripped_name, (leaf, list()))[
|
|
210
|
+
1
|
|
211
|
+
].append(
|
|
212
|
+
(
|
|
213
|
+
threshold_value,
|
|
214
|
+
bracket,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
if "unit" in leaf.metadata and leaf.metadata["unit"] == "list":
|
|
219
|
+
if value is None:
|
|
220
|
+
leaf.update(start=date_, value=[])
|
|
221
|
+
else:
|
|
222
|
+
values = [
|
|
223
|
+
p.replace(",", "").strip() for p in value.split(" ")
|
|
224
|
+
]
|
|
225
|
+
leaf.update(start=date_, value=[v for v in values if v])
|
|
226
|
+
else:
|
|
227
|
+
leaf.update(start=date_, value=value)
|
|
228
|
+
|
|
229
|
+
for leaf, threshold in params_with_thresholds.values():
|
|
230
|
+
sorted_brackets = [v[1] for v in sorted(threshold, key=lambda x: x[0])]
|
|
231
|
+
leaf.brackets = sorted_brackets
|
|
232
|
+
|
|
233
|
+
return local_parameters
|
|
234
|
+
|
|
235
|
+
self.modify_parameters(modifier_function=modify_parameters)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openfisca-excel-reform
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
8
|
+
Requires-Dist: openfisca-core[web-api]>=43
|
|
9
|
+
|
|
10
|
+
Excel reform provide a way to modify a subtree of parameters with an Excel file.
|
|
11
|
+
|
|
12
|
+
`TestExcelReformIntegration.test_build_reform_excel_and_generate_template` illustrates how this package can be used.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
openfisca_excel_reform/__init__.py
|
|
4
|
+
openfisca_excel_reform.egg-info/PKG-INFO
|
|
5
|
+
openfisca_excel_reform.egg-info/SOURCES.txt
|
|
6
|
+
openfisca_excel_reform.egg-info/dependency_links.txt
|
|
7
|
+
openfisca_excel_reform.egg-info/requires.txt
|
|
8
|
+
openfisca_excel_reform.egg-info/top_level.txt
|
|
9
|
+
tests/test_reform.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openfisca_excel_reform
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [ "setuptools>=61" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openfisca-excel-reform"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Add your description here"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"openpyxl>=3.1.5",
|
|
13
|
+
"openfisca-core[web-api]>=43",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"openfisca-country-template>=8.0.3",
|
|
19
|
+
"pytest>=8,<9.0",
|
|
20
|
+
]
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
from typing import IO
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from datetime import date, datetime, time
|
|
5
|
+
|
|
6
|
+
import openpyxl
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from openfisca_core.parameters.parameter_scale import ParameterScale
|
|
10
|
+
from openfisca_excel_reform import (
|
|
11
|
+
ExcelReform,
|
|
12
|
+
ExcelReformBuilder,
|
|
13
|
+
ExcelReformTemplateGenerator,
|
|
14
|
+
get_parameter_node,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from openfisca_country_template import CountryTaxBenefitSystem
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(scope="module")
|
|
20
|
+
def tax_benefit_system():
|
|
21
|
+
return CountryTaxBenefitSystem()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def to_wb_file(wb: openpyxl.Workbook) -> IO[bytes]:
|
|
25
|
+
bytes = io.BytesIO()
|
|
26
|
+
wb.save(bytes)
|
|
27
|
+
bytes.seek(0)
|
|
28
|
+
return bytes
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def wb():
|
|
33
|
+
wb = openpyxl.Workbook()
|
|
34
|
+
config_sheet = wb.create_sheet("Config")
|
|
35
|
+
wb.create_sheet("Paramètres")
|
|
36
|
+
wb.create_sheet("OtherSheet")
|
|
37
|
+
config_sheet["A2"] = "root"
|
|
38
|
+
config_sheet["B2"] = "benefits"
|
|
39
|
+
|
|
40
|
+
param_sheet = wb.create_sheet("Paramètres_2025")
|
|
41
|
+
param_sheet["A1"] = "Nom"
|
|
42
|
+
param_sheet["B1"] = "Date"
|
|
43
|
+
param_sheet["C1"] = "Valeur"
|
|
44
|
+
|
|
45
|
+
param_sheet.append(["parenting_allowance.amount", date(2025, 1, 1), 100000])
|
|
46
|
+
|
|
47
|
+
return wb
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def wb_file(wb: openpyxl.Workbook) -> IO[bytes]:
|
|
52
|
+
return to_wb_file(wb)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestExcelReformBuilder:
|
|
56
|
+
def test_get_suffixes(self, wb_file):
|
|
57
|
+
builder = ExcelReformBuilder(baseline=None, path_or_file=wb_file)
|
|
58
|
+
suffixes = builder.suffixes
|
|
59
|
+
assert suffixes == ["", "_2025"]
|
|
60
|
+
|
|
61
|
+
def test_get_reform_data(self, wb_file):
|
|
62
|
+
builder = ExcelReformBuilder(baseline=None, path_or_file=wb_file)
|
|
63
|
+
|
|
64
|
+
assert builder.root_name == "benefits"
|
|
65
|
+
assert builder.get_parameters("_2025") == [
|
|
66
|
+
("parenting_allowance.amount", 100000, date(2025, 1, 1))
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def test_build_reform(self, tax_benefit_system, wb_file):
|
|
70
|
+
builder = ExcelReformBuilder(baseline=tax_benefit_system, path_or_file=wb_file)
|
|
71
|
+
reform = builder.build_reform("_2025")
|
|
72
|
+
|
|
73
|
+
assert reform.root_name == "benefits"
|
|
74
|
+
assert reform.reformed_parameters == [
|
|
75
|
+
("parenting_allowance.amount", 100000, date(2025, 1, 1))
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
def test_pas_de_root(self, tax_benefit_system, wb):
|
|
79
|
+
ws = wb["Config"]
|
|
80
|
+
ws.delete_rows(2)
|
|
81
|
+
|
|
82
|
+
builder = ExcelReformBuilder(
|
|
83
|
+
baseline=tax_benefit_system, path_or_file=to_wb_file(wb)
|
|
84
|
+
)
|
|
85
|
+
with pytest.raises(AssertionError):
|
|
86
|
+
builder.build_reform("_2025")
|
|
87
|
+
|
|
88
|
+
def test_pas_de_period(self, tax_benefit_system, wb: openpyxl.Workbook):
|
|
89
|
+
ws = wb["Paramètres_2025"]
|
|
90
|
+
ws.delete_cols(1)
|
|
91
|
+
|
|
92
|
+
builder = ExcelReformBuilder(
|
|
93
|
+
baseline=tax_benefit_system, path_or_file=to_wb_file(wb)
|
|
94
|
+
)
|
|
95
|
+
with pytest.raises(AssertionError):
|
|
96
|
+
builder.build_reform("_2025")
|
|
97
|
+
|
|
98
|
+
def test_get_parameters_shuffled_columns(self, wb):
|
|
99
|
+
param_sheet = wb["Paramètres_2025"]
|
|
100
|
+
param_sheet.move_range("B1:B20", cols=2, rows=0)
|
|
101
|
+
param_sheet.delete_cols(2)
|
|
102
|
+
|
|
103
|
+
builder = ExcelReformBuilder(baseline=None, path_or_file=to_wb_file(wb))
|
|
104
|
+
assert builder.get_parameters("_2025") == [
|
|
105
|
+
("parenting_allowance.amount", 100000, date(2025, 1, 1))
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
def test_get_parameters_with_two_periods(self, wb: openpyxl.Workbook):
|
|
109
|
+
param_sheet = wb["Paramètres_2025"]
|
|
110
|
+
param_sheet["A3"] = param_sheet["A2"].value
|
|
111
|
+
param_sheet["B3"] = date(2026, 1, 1)
|
|
112
|
+
param_sheet["C3"] = 200000
|
|
113
|
+
|
|
114
|
+
builder = ExcelReformBuilder(baseline=None, path_or_file=to_wb_file(wb))
|
|
115
|
+
assert builder.get_parameters("_2025") == [
|
|
116
|
+
("parenting_allowance.amount", 100000, date(2025, 1, 1)),
|
|
117
|
+
("parenting_allowance.amount", 200000, date(2026, 1, 1)),
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestExcelReform:
|
|
122
|
+
def test_init(self, tax_benefit_system):
|
|
123
|
+
reform = ExcelReform(
|
|
124
|
+
name="name",
|
|
125
|
+
baseline=tax_benefit_system,
|
|
126
|
+
root_name="benefits",
|
|
127
|
+
reformed_parameters=[
|
|
128
|
+
("parenting_allowance.amount", 1000.0, date(2025, 1, 1))
|
|
129
|
+
],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
assert reform.root_name == "benefits"
|
|
133
|
+
assert reform.reformed_parameters == [
|
|
134
|
+
("parenting_allowance.amount", 1000.0, date(2025, 1, 1))
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
parameter, _ = get_parameter_node(
|
|
138
|
+
reform.parameters, "benefits.parenting_allowance.amount"
|
|
139
|
+
)
|
|
140
|
+
assert parameter.get_at_instant("2025-01-01") == 1000.0
|
|
141
|
+
|
|
142
|
+
def test_partials_parameters(self, tax_benefit_system):
|
|
143
|
+
reform = ExcelReform(
|
|
144
|
+
name="name",
|
|
145
|
+
baseline=tax_benefit_system,
|
|
146
|
+
root_name="benefits",
|
|
147
|
+
reformed_parameters=[
|
|
148
|
+
("basic_income", 800.0, date(2026, 1, 1)),
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
parameter, _ = get_parameter_node(reform.parameters, "benefits.basic_income")
|
|
153
|
+
assert parameter.get_at_instant("2024-06-01") == 600.0
|
|
154
|
+
assert parameter.get_at_instant("2025-06-01") == 600.0
|
|
155
|
+
assert parameter.get_at_instant("2026-06-01") == 800.0
|
|
156
|
+
|
|
157
|
+
parameter2, _ = get_parameter_node(
|
|
158
|
+
reform.parameters, "benefits.parenting_allowance.amount"
|
|
159
|
+
)
|
|
160
|
+
assert parameter2.get_at_instant("2024-06-01") == 600.0
|
|
161
|
+
assert parameter2.get_at_instant("2025-06-01") == 600.0
|
|
162
|
+
|
|
163
|
+
def test_brackets(self, tax_benefit_system):
|
|
164
|
+
reform = ExcelReform(
|
|
165
|
+
name="name",
|
|
166
|
+
baseline=tax_benefit_system,
|
|
167
|
+
root_name="taxes",
|
|
168
|
+
reformed_parameters=[
|
|
169
|
+
("social_security_contribution.0", 0.01, date(2025, 1, 1)),
|
|
170
|
+
("social_security_contribution.3000", 0.03, date(2025, 1, 1)),
|
|
171
|
+
("social_security_contribution.6000", 0.13, date(2025, 1, 1)),
|
|
172
|
+
("social_security_contribution.12400", 0.001, date(2025, 1, 1)),
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
parameter, _ = get_parameter_node(
|
|
176
|
+
reform.parameters, "taxes.social_security_contribution"
|
|
177
|
+
)
|
|
178
|
+
assert isinstance(parameter, ParameterScale)
|
|
179
|
+
p_instant = parameter.get_at_instant("2025-01")
|
|
180
|
+
assert len(p_instant.rates) == 4
|
|
181
|
+
assert len(p_instant.thresholds) == 4
|
|
182
|
+
assert p_instant.rates == [0.01, 0.03, 0.13, 0.001]
|
|
183
|
+
assert p_instant.thresholds == [0.0, 3000.0, 6000.0, 12400.0]
|
|
184
|
+
|
|
185
|
+
def test_unsorted_brackets(self, tax_benefit_system):
|
|
186
|
+
reform = ExcelReform(
|
|
187
|
+
name="name",
|
|
188
|
+
baseline=tax_benefit_system,
|
|
189
|
+
root_name="taxes",
|
|
190
|
+
reformed_parameters=[
|
|
191
|
+
("social_security_contribution.6000", 0.13, date(2025, 1, 1)),
|
|
192
|
+
("social_security_contribution.0", 0.01, date(2025, 1, 1)),
|
|
193
|
+
("social_security_contribution.12400", 0.001, date(2025, 1, 1)),
|
|
194
|
+
("social_security_contribution.3000", 0.03, date(2025, 1, 1)),
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
parameter, _ = get_parameter_node(
|
|
198
|
+
reform.parameters, "taxes.social_security_contribution"
|
|
199
|
+
)
|
|
200
|
+
assert isinstance(parameter, ParameterScale)
|
|
201
|
+
p_instant = parameter.get_at_instant("2025-01")
|
|
202
|
+
assert len(p_instant.rates) == 4
|
|
203
|
+
assert len(p_instant.thresholds) == 4
|
|
204
|
+
assert p_instant.rates == [0.01, 0.03, 0.13, 0.001]
|
|
205
|
+
assert p_instant.thresholds == [0.0, 3000.0, 6000.0, 12400.0]
|
|
206
|
+
|
|
207
|
+
def test_parameters_with_date(self, tax_benefit_system):
|
|
208
|
+
reform = ExcelReform(
|
|
209
|
+
name="name",
|
|
210
|
+
baseline=tax_benefit_system,
|
|
211
|
+
root_name="benefits",
|
|
212
|
+
reformed_parameters=[
|
|
213
|
+
("basic_income", 500.0, date(2025, 1, 1)),
|
|
214
|
+
("basic_income", 600.0, date(2026, 1, 1)),
|
|
215
|
+
],
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
parameter, _ = get_parameter_node(reform.parameters, "benefits.basic_income")
|
|
219
|
+
assert parameter.get_at_instant("2025-06-01") == 500.0
|
|
220
|
+
assert parameter.get_at_instant("2026-06-01") == 600.0
|
|
221
|
+
|
|
222
|
+
def test_bracket_parameters_with_date(self, tax_benefit_system):
|
|
223
|
+
reform = ExcelReform(
|
|
224
|
+
name="name",
|
|
225
|
+
baseline=tax_benefit_system,
|
|
226
|
+
root_name="taxes",
|
|
227
|
+
reformed_parameters=[
|
|
228
|
+
("social_security_contribution.0", 0.02, date(2025, 1, 1)),
|
|
229
|
+
("social_security_contribution.6000", 0.06, date(2025, 1, 1)),
|
|
230
|
+
("social_security_contribution.12400", 0.12, date(2025, 1, 1)),
|
|
231
|
+
("social_security_contribution.0", 0.03, date(2026, 1, 1)),
|
|
232
|
+
("social_security_contribution.6000", 0.07, date(2026, 1, 1)),
|
|
233
|
+
("social_security_contribution.12400", 0.13, date(2026, 1, 1)),
|
|
234
|
+
],
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
parameter, _ = get_parameter_node(
|
|
238
|
+
reform.parameters, "taxes.social_security_contribution"
|
|
239
|
+
)
|
|
240
|
+
assert isinstance(parameter, ParameterScale)
|
|
241
|
+
|
|
242
|
+
p_instant_2025 = parameter.get_at_instant("2025-06")
|
|
243
|
+
assert p_instant_2025.rates == [0.02, 0.06, 0.12]
|
|
244
|
+
|
|
245
|
+
p_instant_2026 = parameter.get_at_instant("2026-06")
|
|
246
|
+
assert p_instant_2026.rates == [0.05, 0.13, 0.25]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class TestExcelReformTemplateGenerator:
|
|
250
|
+
def test_generate_parameter_data(self, tax_benefit_system):
|
|
251
|
+
period = date(2025, 1, 1)
|
|
252
|
+
generator = ExcelReformTemplateGenerator(tax_benefit_system, "benefits", period)
|
|
253
|
+
parameters = generator.parameter_data()
|
|
254
|
+
assert parameters == [
|
|
255
|
+
("basic_income", 600.0),
|
|
256
|
+
("housing_allowance", None),
|
|
257
|
+
("parenting_allowance.amount", 600.0),
|
|
258
|
+
("parenting_allowance.income_threshold", 500),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
def test_generate_parameter_data_with_scale(self, tax_benefit_system):
|
|
262
|
+
root_name = "taxes"
|
|
263
|
+
period = date(2025, 1, 1)
|
|
264
|
+
generator = ExcelReformTemplateGenerator(tax_benefit_system, root_name, period)
|
|
265
|
+
|
|
266
|
+
io_bytes = io.BytesIO()
|
|
267
|
+
generator.save_template_xlsx(io_bytes)
|
|
268
|
+
|
|
269
|
+
io_bytes.seek(0)
|
|
270
|
+
wb = openpyxl.load_workbook(io_bytes)
|
|
271
|
+
|
|
272
|
+
config_sheet = wb["Config"]
|
|
273
|
+
assert config_sheet["A2"].value == "root"
|
|
274
|
+
assert config_sheet["B2"].value == root_name
|
|
275
|
+
|
|
276
|
+
param_sheet = wb["Paramètres"]
|
|
277
|
+
rows = list(param_sheet.iter_rows(values_only=True))
|
|
278
|
+
|
|
279
|
+
# Deal with Excel date to datetime conversion
|
|
280
|
+
output_period = datetime.combine(period, time())
|
|
281
|
+
assert rows == [
|
|
282
|
+
("Nom", "Date", "Valeur"),
|
|
283
|
+
("housing_tax.minimal_amount", output_period, 200),
|
|
284
|
+
("housing_tax.rate", output_period, 10),
|
|
285
|
+
("income_tax_rate", output_period, 0.15),
|
|
286
|
+
("social_security_contribution.0.0", output_period, 0.02),
|
|
287
|
+
("social_security_contribution.12400.0", output_period, 0.12),
|
|
288
|
+
("social_security_contribution.6000.0", output_period, 0.06),
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TestExcelReformIntegration:
|
|
293
|
+
def test_build_reform_excel_and_generate_template(self, tax_benefit_system):
|
|
294
|
+
root_name = "taxes"
|
|
295
|
+
period = date(2025, 1, 1)
|
|
296
|
+
generator = ExcelReformTemplateGenerator(tax_benefit_system, root_name, period)
|
|
297
|
+
|
|
298
|
+
io_bytes = io.BytesIO()
|
|
299
|
+
generator.save_template_xlsx(io_bytes)
|
|
300
|
+
|
|
301
|
+
io_bytes.seek(0)
|
|
302
|
+
## Here you could update the Excel file
|
|
303
|
+
|
|
304
|
+
builder = ExcelReformBuilder(baseline=tax_benefit_system, path_or_file=io_bytes)
|
|
305
|
+
assert builder.suffixes == [""]
|
|
306
|
+
|
|
307
|
+
reform = builder.build_reform(builder.suffixes[0])
|
|
308
|
+
assert reform.root_name == root_name
|