Skip to content
Merged
51 changes: 0 additions & 51 deletions docs/geps/gep-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,57 +320,6 @@ The following walks through several cases.
that the (set of) parameter(s) is not relevant any more, else the previous ones will
linger on.

(gep-3-keys-referring-to-functions)=

## Keys referring to functions

### The `rounding` key

See {ref}`GEP-5 <gep-5>` for the entire scope of rounding, here we reproduce the
{ref}`relevant section referring to YAML-files <gep-5-rounding-spec-yaml>`,

The following goes through the details using an example from the basic pension allowance
(Grundrente).

The law on the public pension insurance specifies that the maximum possible
Grundrentenzuschlag `sozialversicherung__rente__grundrente__höchstbetrag_m` be rounded
to the nearest fourth decimal point (§76g SGB VI: Zuschlag an Entgeltpunkten für
langjährige Versicherung). The example below contains GETTSIM's encoding of this fact.

The snippet is taken from `ges_rente.yaml`, which contains the following code:

```yaml
rounding:
höchstbetrag_m:
2020-01-01:
base: 0.0001
direction: nearest
reference: §76g SGB VI Abs. 4 Nr. 4
```

The specification of the rounding parameters starts with the key `rounding` at the
outermost level of indentation. The keys are names of functions.

At the next level, the `YYYY-MM-DD` key(s) indicate when rounding was introduced and/or
changed. This is done in in the same way as for other policy parameters. Those
`YYYY-MM-DD` key(s) are associated with a dictionary containing the following elements:

- The parameter `base` determines the base to which the variables is rounded. It has to
be a floating point number.
- The parameter `direction` has to be one of `up`, `down`, or `nearest`.
- The `reference` must contain the reference to the law, which specifies the rounding.

### The `dates_active` key

Some functions should not be present at certain times. For example, `arbeitsl_geld_2`
and all its ancestors should not appear in DAGs referring to years prior to 2005.

Other functions have different interfaces in different years or undergo very large
changes in their body.

The `dates_active` key can be used to include certain functions only in certain years
and to switch between different implementations of other functions.

(gep-3-storage-of-parameters)=

## Storage of parameters
Expand Down
136 changes: 36 additions & 100 deletions docs/geps/gep-05.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,126 +35,62 @@ GETTSIM's default will be 1. This document describes how we support both use cas

## Implementation

GETTSIM allows for optional rounding of functions' results. Rounding parameters are
specified in the `.yaml`-files. The following goes through the details using an example
from the basic pension allowance (Grundrente).
GETTSIM allows for optional rounding of functions' results. Rounding specications are
defined in the `policy_function` decorators. The following goes through the details
using an example from the basic pension allowance (Grundrente).

The law on the public pension insurance specifies that the maximum possible
Grundrentenzuschlag `sozialversicherung__rente__grundrente__höchstbetrag_m` be rounded
to the nearest fourth decimal point (§76g SGB VI: Zuschlag an Entgeltpunkten für
langjährige Versicherung). The example below contains GETTSIM's encoding of this fact.

The snippet is taken from `ges_rente.yaml`, which contains the following code:

```yaml
rounding:
sozialversicherung__rente__grundrente__höchstbetrag_m:
2020-01-01:
base: 0.0001
direction: nearest
reference: §76g SGB VI Abs. 4 Nr. 4
```

The specification of the rounding parameters starts with the key `rounding` at the
outermost level of indentation. The keys are names of functions.

At the next level, the `YYYY-MM-DD` key(s) indicate when rounding was introduced and/or
changed. This is done in in the same way as for other policy parameters, see
{ref}`gep-3`. Those `YYYY-MM-DD` key(s) are associated with a dictionary containing the
following elements:

- The parameter `base` determines the base to which the variables is rounded. It has to
be a floating point number.
- The parameter `direction` has to be one of `up`, `down`, or `nearest`.
- The `reference` must contain the reference to the law, which specifies the rounding.

In the same way as other policy parameters, the rounding parameters become part of the
dictionary `policy_params`.

A function to be rounded must be decorated with `policy_function`. Set the
`params_key_for_rounding` parameter to point to the key of the policy parameters
dictionary containing the rounding parameters relating to the function that is
decorated. In the above example, the rounding specification for
`sozialversicherung__rente__grundrente__höchstbetrag_m` will be found in
`policy_params["ges_rente"]` after {func}`set_up_policy_environment()` has been called
(since it was specified in `ges_rente.yaml`). Hence, the `params_key_for_rounding`
argument of `policy_function` has to be `"ges_rente"`:
The snippet is taken from `sozialversicherung/rente/grundrente/grundrente.py`, which
contains the following code:

```python
@policy_function(params_key_for_rounding="ges_rente")
def sozialversicherung__rente__grundrente__höchstbetrag_m(
sozialversicherung__rente__grundrente__grundrentenzeiten_monate: int,
) -> float:
...
return out
from ttsim import policy_function, RoundingSpec, RoundingDirection


@policy_function(
rounding_spec=RoundingSpec(
base=0.0001,
direction=RoundingDirection.NEAREST,
reference="§76g SGB VI Abs. 4 Nr. 4",
),
start_date="2021-01-01",
)
def höchstbetrag_m(
grundrentenzeiten_monate: int,
ges_rente_params: dict,
) -> float: ...
```

When calling
{func}`compute_taxes_and_transfers <ttsim.compute_taxes_and_transfers.compute_taxes_and_transfers>`
with `rounding=True`, GETTSIM will look for a key `"rounding"` in
`policy_params["params_key"]` and within that, for another key containing the decorated
function's name (here: `"sozialversicherung__rente__grundrente__höchstbetrag_m"`). That
is, by the machinery outlined in {ref}`GEP 3 <gep-3>`, the following indexing of the
`policy_params` dictionary
The specification of the rounding parameters is defined via the `RoundingSpec` class.
`RoundingSpec` takes the following inputs:

```python
policy_params["ges_rente"]["rounding"][
"sozialversicherung__rente__grundrente__höchstbetrag_m"
]
```

needs to be possible and yield the `"base"` and `"direction"` keys as described above.
- The `base` determines the base to which the variables is rounded. It has to be a
floating point number.
- The `direction` has to be one of `RoundingDirection.UP`, `RoundingDirection.DOWN`,
`RoundingDirection.NEAREST`.
- The `reference` provides the legal reference for the rounding rule. This is optional.
- Additionally, via the `to_add_after_rounding` input, users can specify some amount
that should be added after the rounding is done (this was relevant for the income tax
before 2004).

Note that GETTSIM only allows for optional rounding of functions' results. In case one
is tempted to write a function requiring an intermediate variable to be rounded, the
function should be split up so that another function returns the quantity to be rounded.

### Error handling

In case a function has a `__params_key_for_rounding__`, but the respective parameters
are missing in `policy_params`, an error is raised.

Note that if the results have to be rounded in some years, but not in others (e.g. after
a policy reform) the rounding parameters (both `"base"` and `"direction"`) must be set
to `None`. This allows that the rounding parameters are found and no error is raised,
but still no rounding is applied.

In case rounding parameters are specified and the function does not have a
`__params_key_for_rounding__` attribute, execution will not lead to an error. This will
never happen in the GETTSIM codebase, however, due to a suitable test.

### User-specified rounding

If a user wants to change rounding of a specified function, she will need to adjust the
rounding parameters in `policy_params`.

Suppose one would like to specify a reform in which
`sozialversicherung__rente__grundrente__höchstbetrag_m` is rounded to the next-lowest
fourth decimal point instead of to the nearest. In that case, the rounding parameters
will need to be changed as follows

```python
policy_params["ges_rente"]["rounding"][
"sozialversicherung__rente__grundrente__höchstbetrag_m"
]["direction"] = "down"
```

This will be done after the policy environment has been set up and it is exactly the
same as for other parameters of the taxes and transfers system, see {ref}`gep-3`.

If a user would like to add user-written functions which should be rounded, she will
need to decorate the respective functions with `policy_function` and adjust
`policy_params` accordingly.

## Advantages of this implementation

This implementation was chosen over alternatives (e.g., specifying the rounding
parameters in the `.py` files directly) for the following reason:
This implementation was chosen over alternatives (e.g., specifying rounding rules in the
parameter files) for the following reason:

- How a variable is rounded is a feature of the taxes and transfers system. Hence, the
best place to define it is alongside its other features.
- Rounding rules are not a parameter, but a function property that we want to turn off
an one. Hence, it makes sense to define it at the function level.
- Rounding parameters might change over time. In this case, the rounding parameters for
each period can be specified in the parameter file using a well-established machinery.
each period can be specified using the `start_date`, `end_date` keywords in the
`policy_function` decorator.
- Optional rounding can be easily specified for user-written functions.
- At the definition of a function, it is clearly visible whether it is optionally
rounded and where the rounding parameters are found.
Expand Down
1 change: 0 additions & 1 deletion src/_gettsim/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"abgelt_st",
"wohngeld",
"kinderzuschl",
"kinderzuschl_eink",
"kindergeld",
"elterngeld",
"ges_rente",
Expand Down
10 changes: 8 additions & 2 deletions src/_gettsim/einkommensteuer/abzüge/sonderausgaben.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Tax allowances for special expenses."""

from ttsim import AggregateByPIDSpec, AggregationType, policy_function
from ttsim import (
AggregateByPIDSpec,
AggregationType,
RoundingDirection,
RoundingSpec,
policy_function,
)

aggregation_specs = {
"betreuungskosten_elternteil_m": AggregateByPIDSpec(
Expand Down Expand Up @@ -101,7 +107,7 @@ def ausgaben_für_betreuung_y(
return out


@policy_function(params_key_for_rounding="eink_st_abzuege")
@policy_function(rounding_spec=RoundingSpec(base=1, direction=RoundingDirection.UP))
def absetzbare_betreuungskosten_y_sn(
ausgaben_für_betreuung_y_sn: float,
eink_st_abzuege_params: dict,
Expand Down
18 changes: 13 additions & 5 deletions src/_gettsim/einkommensteuer/abzüge/vorsorgeaufwendungen.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from ttsim import policy_function
from ttsim import RoundingDirection, RoundingSpec, policy_function


@policy_function(
end_date="2004-12-31",
leaf_name="vorsorgeaufwendungen_y_sn",
params_key_for_rounding="eink_st_abzuege",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.UP, reference="§ 10 Abs. 3 EStG"
),
)
def vorsorgeaufwendungen_y_sn_bis_2004(
vorsorgeaufwendungen_regime_bis_2004_y_sn: float,
Expand All @@ -27,7 +29,9 @@ def vorsorgeaufwendungen_y_sn_bis_2004(
start_date="2005-01-01",
end_date="2009-12-31",
leaf_name="vorsorgeaufwendungen_y_sn",
params_key_for_rounding="eink_st_abzuege",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.UP, reference="§ 10 Abs. 3 EStG"
),
)
def vorsorgeaufwendungen_y_sn_ab_2005_bis_2009(
vorsorgeaufwendungen_regime_bis_2004_y_sn: float,
Expand Down Expand Up @@ -59,7 +63,9 @@ def vorsorgeaufwendungen_y_sn_ab_2005_bis_2009(
start_date="2010-01-01",
end_date="2019-12-31",
leaf_name="vorsorgeaufwendungen_y_sn",
params_key_for_rounding="eink_st_abzuege",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.UP, reference="§ 10 Abs. 3 EStG"
),
)
def vorsorgeaufwendungen_y_sn_ab_2010_bis_2019(
vorsorgeaufwendungen_regime_bis_2004_y_sn: float,
Expand Down Expand Up @@ -90,7 +96,9 @@ def vorsorgeaufwendungen_y_sn_ab_2010_bis_2019(
@policy_function(
start_date="2020-01-01",
leaf_name="vorsorgeaufwendungen_y_sn",
params_key_for_rounding="eink_st_abzuege",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.UP, reference="§ 10 Abs. 3 EStG"
),
)
def vorsorgeaufwendungen_y_sn_ab_2020(
vorsorgeaufwendungen_keine_kappung_krankenversicherung_y_sn: float,
Expand Down
26 changes: 21 additions & 5 deletions src/_gettsim/einkommensteuer/einkommensteuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from ttsim import (
AggregateByPIDSpec,
AggregationType,
RoundingDirection,
RoundingSpec,
piecewise_polynomial,
policy_function,
)
Expand All @@ -22,7 +24,11 @@


@policy_function(
end_date="1996-12-31", leaf_name="betrag_y_sn", params_key_for_rounding="eink_st"
end_date="1996-12-31",
leaf_name="betrag_y_sn",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.DOWN, reference="§ 32a Abs. 1 S. 6 EStG"
),
)
def betrag_y_sn_kindergeld_kinderfreibetrag_parallel(
betrag_mit_kinderfreibetrag_y_sn: float,
Expand All @@ -45,7 +51,9 @@ def betrag_y_sn_kindergeld_kinderfreibetrag_parallel(
@policy_function(
start_date="1997-01-01",
leaf_name="betrag_y_sn",
params_key_for_rounding="eink_st",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.DOWN, reference="§ 32a Abs. 1 S.6 EStG"
),
)
def betrag_y_sn_kindergeld_oder_kinderfreibetrag(
betrag_ohne_kinderfreibetrag_y_sn: float,
Expand Down Expand Up @@ -109,7 +117,9 @@ def kinderfreibetrag_günstiger_sn(
@policy_function(
end_date="2001-12-31",
leaf_name="betrag_mit_kinderfreibetrag_y_sn",
params_key_for_rounding="eink_st",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.DOWN, reference="§ 32a Abs. 1 S.6 EStG"
),
)
def betrag_mit_kinderfreibetrag_y_sn_bis_2001() -> float:
raise NotImplementedError("Tax system before 2002 is not implemented yet.")
Expand All @@ -118,7 +128,9 @@ def betrag_mit_kinderfreibetrag_y_sn_bis_2001() -> float:
@policy_function(
start_date="2002-01-01",
leaf_name="betrag_mit_kinderfreibetrag_y_sn",
params_key_for_rounding="eink_st",
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.DOWN, reference="§ 32a Abs. 1 S.6 EStG"
),
)
def betrag_mit_kinderfreibetrag_y_sn_ab_2002(
zu_versteuerndes_einkommen_mit_kinderfreibetrag_y_sn: float,
Expand Down Expand Up @@ -151,7 +163,11 @@ def betrag_mit_kinderfreibetrag_y_sn_ab_2002(
return out


@policy_function(params_key_for_rounding="eink_st")
@policy_function(
rounding_spec=RoundingSpec(
base=1, direction=RoundingDirection.DOWN, reference="§ 32a Abs. 1 S.6 EStG"
)
)
def betrag_ohne_kinderfreibetrag_y_sn(
gesamteinkommen_y: float,
anzahl_personen_sn: int,
Expand Down
Loading
Loading