GEP 3 — Parameters of the taxes and transfers system#

Author

Hans-Martin von Gaudecker

Status

Provisional

Type

Standards Track

Created

2022-03-28

Updated

2025-07-23

Resolution

Accepted

Abstract#

This GEP describes the structure of the parameters of the taxes and transfers system. This includes the format of the yaml files (initial input) and storage of the processed parameters.

Motivation and Scope#

The parameters of the taxes and transfers system are among the core elements of GETTSIM. Along with the functions operating on them and the input data, they determine all quantities of interest. Sensibly structuring and cleanly documenting the meaning and sources of these parameters requires particular care.

Usage and Impact#

GETTSIM developers should closely look at the Section Structure of the YAML files before adding new parameters. Some validation happens via the pre-commit hooks, but that cannot catch all inconsistencies.

Structure of the YAML files#

Each YAML file contains a number of parameters at the outermost level of indentation. Each of these parameters is a dictionary with at least 6 keys: name, description, unit, reference_period, type and the YYYY-MM-DD-formatted date on which it first took effect.

Values usually change over time; each time a value is changed, another YYYY-MM-DD entry is added. Beyond that, no additional keys are allowed.

  1. The name key has two sub-keys de and en, which are

    • short names without re-stating the realm of the parameter (e.g. “Arbeitslosengeld II” or “Kinderzuschlag”);

    • not sentences;

    • correctly capitalised.

    Example (from kindergeld):

    altersgrenze:
      name:
        de: Alter, ab dem Kindergeld nicht mehr gezahlt wird.
        en: Age at which child benefit is no longer paid.
    
  2. The description key has two sub-keys de and en, which

    • are exhaustive explanations of the parameter;

    • show the § and Gesetzbuch of that parameter (including its entire history if the location has changed);

    • mention bigger amendments / Neufassungen and be as helpful as possible to make sense of that parameter.

      Example:

      altersgrenze:
        description:
          de: >-
            § 32 Art. 2-4 EStG.
            Für minderjährige Kinder besteht ohne Bedingungen ein Anspruch auf Kindergeld.
            Auch für erwachsene Kinder kann bis zu einer Altersgrenze unter bestimmten
            Bedingungen ein Anspruch auf Kindergeld bestehen.
          en: >-
            § 32 Art. 2-4 EStG.
            Underage children are entitled to child benefit without any conditions. Also adult
            children up to a specified age are entitled to child benefit under certain
            conditions.
      
  3. The unit key informs on the unit of the values (Euro or DM if monetary, share of some other value, …).

    • In some cases (e.g., factor for the calculation of the marginal employment threshold), there is no unit.

    • It should be capitalised.

    • Possible values:

      • Euros,

      • DM,

      • Share,

      • Percent,

      • Years,

      • Months,

      • Hours,

      • Square Meters,

      • Euros / Square Meter,

      • None.

    Example:

    altersgrenze:
      name:
        de: Alter, ab dem Kindergeld nicht mehr gezahlt wird.
      unit: Euro
    
  4. The type key signals to GETTSIM how the parameter is to be interpreted. It must be specified as one of:

    • scalar,

    • dict,

    • piecewise_constant,

    • piecewise_linear,

    • piecewise_quadratic,

    • piecewise_cubic,

    • birth_month_based_phase_inout

    • birth_year_based_phase_inout,

    • require_converter,

    scalar is self-explanatory; dict must be a homogeneous dictionary with string or integer keys and scalar values (int, float, bool).

    piecewise_constant, piecewise_linear, piecewise_quadratic, piecewise_cubic will be converted automatically to be used with the piecewise_polynomial function.

    birth_month_based_phase_inout and birth_year_based_phase_inout are used to phase in or out a parameter based on the birth year of the individual. They are automatically converted to be used as ConsecutiveIntLookupTableParamValue objects.

    require_converter can be anything. However there must be a converter function in the codebase.

  5. The reference_period key informs on the reference period of the values, if applicable. Possible values:

    • Year,

    • Quarter,

    • Month,

    • Week,

    • Day,

    • Hour,

    • None

  6. The optional add_jahresanfang can be used to make the parameter that is relevant at the start of the year (relative to the date for which the policy environment is set up) available to GETTSIM functions.

    If specified, two parameters will be available:

    ("path", "to", "parameter")
    ("path", "to", "parameter_jahresanfang")
    

    Example from sozialversicherung / arbeitslosen / beitragssatz.yaml:

    beitragssatz:
      name:
        de: Beitragssatz zur Arbeitslosenversicherung
      unit: Share
      reference_period: null
      type: scalar
      add_jahresanfang: true
    
  7. The YYYY-MM-DD key(s)

    • hold all historical values for a specific parameter or set of parameters in dictionaries

    • contain a precise reference to the law in the reference subkey;

    • may add additional descriptions in the note key;

    • is present with a note or reference only if a parameter ceases to exist starting on a particular date;

    • in case of a scalar type, the key of the scalar is value.

    The remainder of this section explains this element in much more detail.

The reference key of [YYYY-MM-DD]#

  • cites the law (Gesetz “G. v.”), decree (Verordnung “V. v.”) or proclamation (Bekanntmachung “B. v.”) that changes the law

  • uses German language

  • follows the style

    • Artikel [n] [G./V./B.] v. [DD.MM.YYYY] BGBl. I S. [SSSS]. for references published until 2022 (the page should be the first page of the law/decree/proclamation, not the exact page of the parameter)

    • Artikel [n] [G./V./B.] v. [DD.MM.YYYY] BGBl. [YYYY] I Nr. [NNN]. for references from 2023 on

  • does not add information “geändert durch” (it is always a change) or the date the law comes into force (this would just repeat the date key one level above)

Example:

beitragssatz:
  name:
    de: Beitragssatz zur Arbeitslosenversicherung
  2019-01-01:
    value: 0.0125
    reference: V. v. 21.12.2018 BGBl. I S. 2663

The note key of [YYYY-MM-DD]#

This optional key may contain a free-form note holding any information that may be relevant for the interpretation of the parameter, the implementer, user, …

beitragssatz:
  name:
    de: Beitragssatz zur Arbeitslosenversicherung
  2019-01-01:
    value: 0.0125
    reference: V. v. 21.12.2018 BGBl. I S. 2663
    note: >-
      Set to 0.013 in Art. 2 Nr. 15 G. v. 18.12.2018 BGBl. I S. 2651. Temporarily
      reduced to 0.0125 in BeiSaV 2019.

The updates_previous key of [YYYY-MM-DD]#

Often laws change only part of a parameter. To avoid error-prone code duplication, we allow for such cases via updates_previous key.

This must not be used with a scalar parameter type. Furthermore, it cannot be used in the first period a parameter is defined.

Example from sozialversicherung / minijob.yaml:

minijobgrenze_ost_west_unterschied
  name:
    de: Minijobgrenze
  unit: Euros
  reference_period: Month
  type: dict
  1997-01-01:
    west: 312
    ost: 266
  1998-01-01:
    updates_previous: true
    west: 317

The values of [YYYY-MM-DD]#

The general idea is to make the replication of the laws very obvious. If the law includes a table, we will have a dictionary with keys 0, 1, 2, …. If the law includes a formula, the formula should be included and its parameters referenced. Etc..

The following walks through several cases.

  • The simplest case is a single parameter, which should be specified as:

    minijobgrenze:
      name:
        de: Minijobgrenze
        en: Thresholds for marginal employment (minijobs)
      description:
        de: Minijob § 8 (1) Nr. 1 SGB IV
        en: Minijob § 8 (1) Nr. 1 SGB IV
      unit: Euros
      reference_period: Month
      type: scalar
      1984-01-01:
        value: 199
      1985-01-01:
        value: 205
      1986-01-01:
        value: 210
      1987-01-01:
        value: 220
      1988-01-01:
        value: 225
      1989-01-01:
        value: 230
      1990-01-01:
        note: >-
          Minijobgrenze differs between West and East Germany. See
          ``parameter_minijobgrenze_ost_west_unterschied``.
      2000-01-01:
        value: 322
      2002-01-01:
        value: 325
      2003-04-01:
        value: 400
      2013-01-01:
        value: 450
      2022-10-01:
        note: Minijob thresholds now calculated based on statutory minimum wage
        reference: Art. 7 G. v. 28.06.2022 BGBl. I S. 969
    

    Note that there are different “active periods” for this parameter. The first one lasts from 1984-01-01 to 1989-12-31, after which there were different values in East and West Germany. from 2000-01-01 until 2022-10-01, the parameter is active again. After that, it is superseded by a formula based on the statutory minimum wage.

  • There could be a dictionary, which has to be homogenous in the keys (integers or strings) and values (scalar floating point numbers, integers, or Booleans):

    minijobgrenze_ost_west_unterschied:
      name:
        de: Minijobgrenze, unterschiedlich in Ost und West
        en: Thresholds for marginal employment (minijobs), different in East and West
      description:
        de: Minijob § 8 (1) Nr. 1 SGB IV
        en: Minijob § 8 (1) Nr. 1 SGB IV
      unit: Euros
      reference_period: Month
      type: dict
      1990-01-01:
        west: 240
        ost: 102
      1991-01-01:
        west: 245
        ost: 120
      1992-01-01:
        west: 256
        ost: 153
      1993-01-01:
        west: 271
        ost: 199
      1994-01-01:
        west: 286
        ost: 225
      1995-01-01:
        west: 297
        ost: 240
      1996-01-01:
        west: 302
        ost: 256
      1997-01-01:
        west: 312
        ost: 266
      1998-01-01:
        updates_previous: true
        west: 317
      1999-01-01:
        west: 322
        ost: 271
      2000-01-01:
        note: >-
          Minijob thresholds do not differ between West and East Germany. See
          `minijobgrenze_m`.
    
  • In some cases, a dictionary with numbered keys makes sense. It is important to use these, not lists! The reason is that we always allow for the note and reference keys to be present.

    satz_gestaffelt:
      name:
        de: Kindergeld pro Kind, Betrag je nach Reihenfolge der Kinder.
        en: Child benefit amount, depending on succession of children.
      description:
        de: >-
          § 66 (1) EStG. Identische Werte in §6 (1) BKGG, diese sind aber nur für beschränkt
          Steuerpflichtige relevant (d.h. Ausländer mit Erwerbstätigkeit in Deutschland).
          Für Werte vor 2002, siehe 'BMF - Datensammlung zur Steuerpolitik'
        en: null
      unit: Euros
      reference_period: Month
      type: dict
      2002-01-01:
        1: 154
        2: 154
        3: 154
        4: 179
      2009-01-01:
        reference: Art. 1 G. v. 22.12.2008 BGBl. I S. 2955
        1: 164
        2: 164
        3: 170
        4: 195
    
  • Another example would be referring to the parameters of a piecewise linear function:

    parameter_solidaritätszuschlag:
      name:
        de: Solidaritätszuschlag
        en: null
      description:
        de: >-
          Ab 1995, der upper threshold im Intervall 1 ist nach der Formel
          transition_threshold in soli_st.py berechnet.
        en: null
      unit: Euros
      reference_period: Year
      type: piecewise_linear
      1991-01-01:
        reference: Artikel 1 G. v. 24.06.1991 BGBl. I S. 1318.
        0:
          lower_threshold: -inf
          rate_linear: 0
          intercept_at_lower_threshold: 0
          upper_threshold: 0
        1:
          lower_threshold: 0
          rate_linear: 0.0375
          upper_threshold: inf
    
  • Phase-in or phase-out of age thresholds based on the birth year of the individual (e.g. increasing statutory retirement age thresholds) should be specified as type birth_year_based_phase_inout. The parameter specification is converted to a lookup table that maps a birth year to the age threshold. The conversion requires the following stucture after the YYYY-MM-DD key:

    • first_birthyear_to_consider: The birth year at which the lookup table starts (just choose some birthyear that is far enough in the past).

    • last_birthyear_to_consider: The birth year at which the lookup table ends (just choose some birthyear that is far enough in the future).

    • YYYY entries with the following structure:

      • years: The age threshold in years.

      • months: The age threshold in months.

    Example from sozialversicherung / rente / altersrente / regelaltersrente / altersgrenze.yaml:

    altersgrenze_gestaffelt:
    name:
      de: Gestaffeltes Eintrittsalter für Regelaltersrente nach Geburtsjahr
      en: Staggered normal retirement age (NRA) for Regelaltersrente by birth year
    description:
      de: >-
        § 35 Satz 2 SGB VI
        Regelaltersgrenze ab der Renteneintritt möglich ist. Wenn früher oder später in
        Rente gegangen wird, wird der Zugangsfaktor und damit der Rentenanspruch höher
        oder niedriger, sofern keine Sonderregelungen gelten.
      en: >-
        § 35 Satz 2 SGB VI
        Normal retirement age from which pension can be received. If retirement benefits
        are claimed earlier or later, the Zugangsfaktor and thus the pension entitlement
        is higher or lower unless special regulations apply.
    unit: Years
    reference_period: null
    type: birth_year_based_phase_inout
    2007-04-20:
      reference: RV-Altersgrenzenanpassungsgesetz 20.04.2007. BGBl. I S. 554
      note: >-
        Increase of the early retirement age from 65 to 67 for birth cohort 1947-1964.
        Vertrauensschutz (Art. 56) applies for birth cohorts before 1955 who were in
        Altersteilzeit before January 1st, 2007 or received "Anpassungsgeld für
        entlassene Arbeitnehmer des Bergbaus".
      first_birthyear_to_consider: 1900
      last_birthyear_to_consider: 2031
      1946:
        years: 65
        months: 0
      1947:
        years: 65
        months: 1
      1948:
        years: 65
        months: 2
      1949:
        years: 65
        months: 3
      1950:
        years: 65
        months: 4
      1951:
        years: 65
        months: 5
      1952:
        years: 65
        months: 6
      1953:
        years: 65
        months: 7
      1954:
        years: 65
        months: 8
      1955:
        years: 65
        months: 9
      1956:
        years: 65
        months: 10
      1957:
        years: 65
        months: 11
      1958:
        years: 66
        months: 0
      1959:
        years: 66
        months: 2
      1960:
        years: 66
        months: 4
      1961:
        years: 66
        months: 6
      1962:
        years: 66
        months: 8
      1963:
        years: 66
        months: 10
      1964:
        years: 67
        months: 0
    
  • Phase-in or phase-out of age thresholds based on the birth month of the individual should be specified as type birth_month_based_phase_inout. The parameter specification is the same as for birth_year_based_phase_inout, except that the YYYY entries are followed by MM keys. The MM keys a have the following structure:

    • first_birthmonth_to_consider: The birth month at which the lookup table starts (just choose some birthmonth that is far enough in the past).

    • last_birthmonth_to_consider: The birth month at which the lookup table ends (just choose some birthmonth that is far enough in the future).

    • years: The age threshold in years.

    • months: The age threshold in months.

    Excerpt from sozialversicherung / rente / altersrente / langjährig / altersgrenze.yaml:

    ...
      1989-12-18:
      reference: Rentenreformgesetz 1992. BGBl. I S. 2261 1989 § 41
      note: Increase of full retirement age from 63 to 65 for birth cohort 1938-1943.
      first_birthyear_to_consider: 1900
      last_birthyear_to_consider: 2100
      1937:
        12:
          years: 63
          months: 0
      1938:
        1:
          years: 63
          months: 1
      ...
    
  • Finally, there are parameters that have a more complex structure, which is not as common as piecewise_linear etc. These need to be specified as require_converter.

    Example from arbeitslosengeld_2 / bedarfe.yaml:

    parameter_regelsatz_nach_regelbedarfsstufen:
      name:
        de: Regelsatz mit direkter Angabe für Regelbedarfsstufen
        en: Standard rate with direct specification of "Regelbedarfsstufen"
      description:
        de: >-
          § 20 V SGB II.  Neufassung SGB II § 20 (1a) und (2) durch
          Artikel 6 G. v. 22.12.2016 BGBl. I S. 3159.
          Regelbedafstufen:
          1: Alleinstehender Erwachsener
          2: Erwachsene in Partnerschaft
          3: Erwachsene unter 25 im Haushalt der Eltern
          4: Jugendliche
          5: Ältere Kinder
          6: Jüngste Kinder
        en: >-
          Regelbedarfsstufen:
          1: Single Adult
          2: Adults in a partner relationship
          3: Adults under 25 in the household of their parents
          4: Adolescents
          5: Older children
          6: Youngest children
      unit: Euros
      reference_period: Month
      type: require_converter
      2011-01-01:
        1: 364
        2: 328
        3: 291
        4:
          min_alter: 14
          max_alter: 17
          betrag: 287
        5:
          min_alter: 6
          max_alter: 13
          betrag: 251
        6:
          min_alter: 0
          max_alter: 5
          betrag: 215
        reference: Artikel 1 G. v. 24.03.2011 BGBl. I S. 453.
    
  • In general, a parameter should appear for the first time that it is mentioned in a law, becomes relevant, etc..

    Do not set parameters to some value if they are not relevant yet.

  • If a parameter ceases to be relevant, is superseded by something else, etc., there must be a YYYY-MM-DD key with a note and/or reference key. There must not be other entries except for these two.

    Example:

    parameter_regelsatz_anteilsbasiert:
      name:
        de: Berechnungsgrundlagen für den Regelsatz
      2011-01-01:
        note: Calculation method changed, see regelsatz_nach_regelbedarfsstufen.
    

Handling of parameters in the codebase#

The contents of the YAML files are processed and are a pytree-like structure, similar to the functions. That is, they can be used directly in their namespace (=path to the yaml file excluding the file name) and accessed by absolute paths otherwise.

In this tree, they are specialised to the relevant policy date. Depending on the type of the parameter (see the previous section), the following types are possible:

  • scalar parameters are just floats / ints / Booleans; i.e., simply the value key of the yaml file.

  • dict parameters are homogenous dictionaries with all contents of the YYYY-MM-DD entries except for the note and reference keys.

  • piecewise_constant / piecewise_linear / piecewise_quadratic / piecewise_cubic parameters are converted to PiecewisePolynomialParameter objects.

  • birth_month_based_phase_inout and birth_year_based_phase_inout are converted to ConsecutiveIntLookupTableParamValue objects.

  • require_converter must have a params_function that converts the YYYY-MM-DD entries to a clear type.

Discussion#

Appendix: json-schema for the yaml files#

{
  "$comment": "If you change this file, always change it in both locations:  1. ttsim/src/ttsim/params-schema.json  2. gettsim/docs/geps/params-schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "patternProperties": {
    "^[a-zA-Z0-9äöüßÄÖÜ_]+$": {
      "type": "object",
      "properties": {
        "name": {
          "type": "object",
          "properties": {
            "de": { "type": "string" },
            "en": { "type": ["string", "null"] }
          },
          "required": ["de"],
          "additionalProperties": false
        },
        "description": {
          "type": "object",
          "properties": {
            "de": { "type": "string" },
            "en": { "type": ["string", "null"] }
          },
          "required": ["de"],
          "additionalProperties": false
        },
        "unit": {
          "type": ["string", "null"],
          "enum": [
            "Euros",
            "DM",
            "Share",
            "Percent",
            "Years",
            "Months",
            "Hours",
            "Square Meters",
            "Euros / Square Meter",
            null
          ]
        },
        "type": {
          "type": "string",
          "enum": [
            "scalar",
            "dict",
            "piecewise_constant",
            "piecewise_linear",
            "piecewise_quadratic",
            "piecewise_cubic",
            "consecutive_int_lookup_table",
            "sparse_to_consecutive_int_lookup_table",
            "month_based_phase_inout_of_age_thresholds",
            "year_based_phase_inout_of_age_thresholds",
            "require_converter"
          ]
        },
        "reference_period": {
          "type": ["string", "null"],
          "enum": ["Year", "Quarter", "Month", "Week", "Day", "Hour", null]
        },
        "add_jahresanfang": {"type": "boolean"}
      },
      "patternProperties": {
        "^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-[0-3][0-9]$": {
          "type": "object",
          "properties": {
            "value": {
              "oneOf": [
                { "type": ["number"] },
                { "type": "string", "enum": ["inf", "-inf"] }
              ]
            },
            "reference": { "type": "string" },
            "note": { "type": "string" },
            "deviation_from": { "type": "string" }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": false,
      "allOf": [
        { "required": [
          "name",
          "description",
          "unit",
          "reference_period",
          "type"
        ]
      },
        {
          "minProperties": 1,
          "patternProperties": {
            "^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-[0-3][0-9]$": {}
          }
        }
      ]
    }
  },
  "additionalProperties": false
}