8. Application Exercise – version 1
8.1. The Problem
The table above allows us to calculate the tax in the simplified case of a taxpayer who has only their salary to report. As indicated in note (1), the tax calculated in this way is the tax before three mechanisms:
- the family quotient cap, which applies to high incomes;
- the tax credit and tax reduction that apply to low-income earners;
Thus, the tax calculation includes the following steps [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

We propose to write a program to calculate a taxpayer’s tax in the simplified case of a taxpayer who has only their salary to report:
8.1.1. Calculation of Gross Tax
The gross tax can be calculated as follows:
First, we calculate the taxpayer’s number of shares:
- each parent contributes 1 share;
- the first two children each contribute 1/2 share;
- subsequent children each contribute one share:
The number of shares is therefore:
- nbParts=1+nbEnfants*0.5+(numberOfChildren-2)*0.5 if the employee is unmarried;
- nbParts = 2 + nbChildren * 0.5 + (nbChildren - 2) * 0.5 if he is married;
- where nbEnfants is the number of children;
- we calculate the taxable income R = 0.9 * S, where S is the annual salary;
- the family quotient QF is calculated as nbParts = R / nbParts;
- we calculate the gross tax I based on the following data (2019):
9964 | 0 | 0 |
27,519 | 0.14 | 1,394.96 |
73,779 | 0.3 | 5,798 |
156,244 | 0.4 | 13,913.69 |
0 | 0.45 | 20163.45 |
Each row has 3 fields: field1, field2, field3. To calculate tax I, we find the first row where QF <= field1 and take the values from that row. For example, for a married employee with two children and an annual salary S of 50,000 euros:
Taxable income: R=0.9*S=45,000
Number of shares: nbParts=2+2*0.5=3
Family quotient: QF=45,000/3=15,000
The first line where QF <= field1 is as follows:
Tax I is then equal to 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*3]=2115. The tax is rounded down to the nearest euro.
If the condition QF <= field1 holds starting from the first line, then the tax is zero.
If QF is such that the relationship QF <= field1 is never satisfied, then the coefficients from the last row are used. In this case:
which gives the gross tax I=0.45*R – 20163.45*nbParts.
8.1.2. Family Quotient Cap
To determine whether the family quotient cap QF applies, we recalculate the gross tax without the children. Again, for the married employee with two children and an annual salary S of 50,000 euros:
Taxable income: R=0.9*S=45,000
Number of shares: nbParts=2 (children are no longer counted)
Family quotient: QF=45,000/2=22,500
The first line where QF <= field1 is as follows:
Tax I is then equal to 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*2]=3510.
Maximum child-related benefit: 1551 * 2 = 3102 euros
Minimum tax: 3510 – 3102 = 408 euros
The gross tax with 3 tax brackets, already calculated at 2,115 euros, is higher than the minimum tax of 408 euros, so the family cap does not apply here.
Generally speaking, the gross tax is greater than (tax1, tax2) where:
- [impôt1]: is the gross tax calculated with children;
- [impôt2]: is the gross tax calculated without children and reduced by the maximum allowance (here 1,551 euros per half-share) related to children;
8.1.3. Calculation of the tax reduction

Still for the married employee with two children and an annual salary S of 50,000 euros:
The gross tax (2,115) from the previous step is less than 2,627 euros for a couple (1,595 euros for a single person): the tax reduction therefore applies. It is calculated as follows:
discount = threshold (couple = 1,970 / single = 1,196) – 0.75 * gross tax
discount = 1,970 – 0.75 × 2,115 = 383.75, rounded to 384 euros.
New gross tax = 2,115 – 384 = 1,731 euros
8.1.4. Calculation of the tax reduction

Below a certain threshold, a 20% reduction is applied to the gross tax resulting from the previous calculations. In 2019, the thresholds are as follows:
- single person: 21,037 euros;
- couple: 42,074 euros; (the figure 37,968 used in the example above appears to be incorrect);
This threshold is increased by the value: 3,797 * (number of half-shares contributed by the children).
Again, for the married employee with two children and an annual salary S of 50,000 euros:
- his taxable income (45,000 euros) is below the threshold (42,074 + 2 × 3,797) = 49,668 euros;
- he is therefore entitled to a 20% reduction in his tax: 1,731 * 0.2 = 346.2 euros, rounded to 347 euros;
- the taxpayer’s gross tax becomes: 1,731 – 347 = 1,384 euros;
8.1.5. Calculation of tax net
Our calculation ends here: the net tax due will be 1,384 euros. In reality, the taxpayer may be eligible for other deductions, particularly for donations to public or general interest organizations.
8.1.6. High-Income Cases
Our previous example applies to the majority of employees. However, the tax calculation is different for high-income earners.
8.1.6.1. Cap on the 10% reduction in annual income
In most cases, taxable income is calculated using the formula: R = 0.9 × S, where S is the annual salary. This is known as the 10% reduction. This reduction is capped. In 2019:
- it cannot exceed 12,502 euros;
- it cannot be less than €437;
Let’s consider the case of an unmarried employee with no children and an annual salary of 200,000 euros:
- the 10% reduction is 200,000 euros > 12,502 euros. It is therefore capped at 12,502 euros;
8.1.6.2. Family Quotient Cap
Let’s consider a case where the family cap described in the section |Family Quotient Cap| applies. Let’s take the case of a couple with three children and an annual income of 100,000 euros. Let’s go through the calculation steps again:
- the 10% deduction is 10,000 euros < 12,502 euros. The taxable income R is therefore 100,000 - 10,000 = 90,000 euros;
- the couple has nbParts = 2 + 0.5 × 2 + 1 = 4 shares;
- their family quotient is therefore QF = R / nbParts = 90,000 / 4 = 22,500 euros;
- their gross tax I1 with children is I1 = 0.14 × 90,000 – 1,394.96 × 4 = 7,020 euros;
- his gross tax I2 without children:
- QF=90,000/2=45,000 euros;
- I2 = 0.3 × 90,000 – 5,798 × 2 = 15,404 euros;
- the family quotient cap rule states that the benefit provided by children cannot exceed (1,551 × 4 half-shares) = 6,204 euros. However, here, it is I2 – I1 = 15,404 – 7,020 = 8,384 euros, which is greater than 6,204 euros;
- the gross tax is therefore recalculated as I3 = I2 - 6,204 = 15,404 - 6,204 = 9,200 euros;
This couple will receive neither a tax credit nor a reduction, and their final tax will be 9,200 euros.
8.1.7. Official figures
Tax calculation is complex. Throughout the document, tests will be conducted using the following examples. The results are from the tax administration’s simulator |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|:
Taxpayer | Official Results | Results from the document’s algorithm |
Couple with 2 children and an annual income of 55,555 euros | Tax = 2,815 euros Tax rate = 14% | Tax = €2,814 Tax rate = 14% |
Couple with 2 children and an annual income of 50,000 euros | Tax = €1,385 Tax credit = €720 Reduction = 0 euros Tax rate = 14% | Tax = €1,384 Discount = 384 euros Credit = 347 euros Tax rate = 14% |
Couple with 3 children and an annual income of 50,000 euros | Tax = 0 euros Discount = 384 euros Reduction = €346 Tax rate = 14% | Tax = 0 euros Discount = 720 euros Deduction = 0 euros Tax rate = 14% |
Single with 2 children and an annual income of 100,000 euros | Tax = 19,884 euros Tax credit = 0 euros Deduction = 0 euros Tax rate = 41% | Tax = €19,884 Surcharge = 4,480 euros discount=0 euros Deduction = 0 euros Tax rate = 41% |
Single with 3 children and an annual income of 100,000 euros | Tax = 16,782 euros Tax credit = 0 euros Deduction = 0 euros Tax rate = 41% | Tax = €16,782 Surcharge = 7,176 euros Discount = 0 euros Reduction = 0 euros Tax rate = 41% |
Couple with 3 children and an annual income of 100,000 euros | Tax = €9,200 Tax credit = 0 euros Deduction = 0 euros Tax rate = 30% | Tax = €9,200 Surcharge = 2,180 euros Discount = 0 euros Deduction = 0 euros Tax rate = 30% |
Couple with 5 children and an annual income of 100,000 euros | Tax = €4,230 Tax credit = 0 euros Deduction = 0 euros Tax rate = 14% | Tax = €4,230 Discount = 0 euros Deduction=0 euros Tax rate = 14% |
Single with no children and an annual income of 100,000 euros | Tax = 22,986 euros Tax credit = 0 euros Deduction = 0 euros Tax rate = 41% | Tax = €22,986 Surcharge = 0 euros Discount = 0 euros Deduction = 0 euros Tax rate = 41% |
Couple with 2 children and an annual income of 30,000 euros | Tax = 0 euros Tax credit = 0 euros Deduction = 0 euros Tax rate = 0% | Tax = 0 euros Discount = 0 euros Deduction=0 euro Tax rate = 0% |
Single with no children and an annual income of 200,000 euros | Tax = 64,211 euros Tax credit = 0 euros Deduction = 0 euros Tax rate = 45% | Tax = €64,210 Surcharge = 7,498 euros Discount = 0 euros Reduction = 0 euros Tax rate = 45% |
Couple with 3 children and an annual income of 200,000 euros | Tax = €42,843 Tax credit = 0 euros Deduction = 0 euros Tax rate = 41% | Tax = €42,842 Surcharge = 17,283 euros Discount = 0 euros Reduction = 0 euros Tax rate = 41% |
In the example above, the “surcharge” refers to the additional amount paid by high-income earners due to two factors:
- the cap on the 10% deduction from annual income;
- the cap on the family quotient;
This indicator could not be verified because the tax authority’s simulator does not provide it.
We can see that the document’s algorithm calculates the correct tax amount every time, though with a margin of error of 1 euro. This margin of error stems from rounding. All monetary amounts are rounded up to the nearest euro in some cases and down to the nearest euro in others. Since I was not familiar with the official rules, the monetary amounts in the document’s algorithm were rounded:
- up to the next euro for discounts and reductions;
- rounded down to the nearest euro for surcharges and the final tax amount;
We will develop several versions of the tax calculation application.
8.2. Version 1

8.2.1. The main script
We present an initial program where:
- the data needed to calculate the tax is hard-coded in the code as lists and constants;
- taxpayer data (married, children, salary) is in a first text file [taxpayersdata.txt];
- the results of the tax calculation (married, children, salary, tax) are stored in a second text file [résultats.txt];
The script [v-01/main.py] is as follows:
# modules
import sys
from impots.v01.shared.impôts_module_01 import *
# hand -----------------------
# constants
# taxpayer file
DATA = "./data/taxpayersdata.txt"
# results file
RESULTATS = "./data/résultats.txt"
try:
# reading taxpayer data
tax_payers = get_taxpayers_data(DATA)
# results list
results = []
# taxpayers' taxes are calculated
for tax_payer in tax_payers:
# tax calculation returns a dictionary of keys
# ['marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
result = calcul_impôt(tax_payer['marié'], tax_payer['enfants'], tax_payer['salaire'])
# the dictionary is added to the list of results
results.append(result)
# we record the results
record_results(RESULTATS, results)
except BaseException as erreur:
# there may be various errors: no file, incorrect file content
# display the error and exit the application
print(f"l'erreur suivante s'est produite : {erreur}]\n")
sys.exit()
Notes
- line 4: we use the [impots.v01.modules.impôts_module_01] module. Note that this path is relative to the root of the PyCharm project;
- line 10: the [data/taxpayersdata.txt] file is as follows:
Each line represents a tuple of three elements [marié / pacsé ou pas, nombre d'enfants, salaire annuel en euros].
- Line 12: the file where the tax calculation results for each taxpayer in the [taxpayersdata.txt] file will be placed. It will have the following content:
- line 16: retrieve taxpayer data from [taxpayersdata.txt]. Retrieve a list of [marié, enfants, salaire] key dictionaries, where each dictionary represents a taxpayer;
- lines 17–25: We calculate the tax for the taxpayers in the list [taxPayers]. We retrieve a list [results], where each element is again a dictionary of keys [marié, enfants, salaire, impôt, surcôte, décôte, réduction, taux];
- line 27: the list [results] of results is saved to the file [résultats.txt] in the format shown above;
- lines 28–32: all exceptions that may occur in the [impots.v01.modules.impôts_module_01] module are caught;
We will now detail the three functions used by the [main] script:
- [get_taxpayers_data]: to read taxpayer data;
- [calcul_impôt]: to calculate their tax;
- [record_results]: to save the results to a text file;
All these functions are located in the [impots.modules.impôts_module_01] module.
8.2.2. The [impots.v01.shared.impôts_module_01] module
The functions required for tax calculation have been grouped in the [impots.v01.shared.impôts_module_01] module:

- in [1]: definition of tax calculation constants;
- in [2]: the list of functions in the module;
8.2.3. The function [get_taxpayers_data]
The function [get_taxpayers_data] is as follows:
# imports
import codecs
…
# reading taxpayer data
# ----------------------------------------
def get_taxpayers_data(taxpayers_filename: str) -> list:
# reading taxpayer data
file = None
try:
# list of taxpayers
taxpayers = []
# open file
file = codecs.open(taxpayers_filename, "r", "utf8")
# we read the first line of the taxpayer file
ligne = file.readline().strip()
# as long as there's a line left to operate
while ligne != '':
# we retrieve the 3 fields married,children,salary which form the line
(marié, enfants, salaire) = ligne.split(",")
# we add them to the list of taxpayers
taxpayers.append({'marié': marié.strip().lower(), 'enfants': int(enfants), 'salaire': int(salaire)})
# a new line is read from the taxpayer file
ligne = file.readline().strip()
# we return the result
return taxpayers
finally:
# close the file if it has been opened
if file:
file.close()
Notes
- line 7: [taxpayers_filename] is the name of the file to be processed. The function returns a list;
- lines 18–24: the loop processing the lines [marié, enfants, salaire] from the text file;
- line 20: the three elements of the line are retrieved. We assume here that the line is syntactically correct, i.e., that it does indeed contain the three expected elements;
- line 22: a dictionary is constructed with the keys [marié, enfants, salaire], and this dictionary is added to the list [taxPayers];
- line 26: once the file has been processed, the list [taxPayers] is returned;
- Lines 10–30: Note that we did not include a [catch] clause in the [try] on line 10. The [catch] clause is not required. Line 27: a [finally] clause has been added to close the text file in all cases, whether an error occurs or not;
- this try/finally structure allows a potential exception to escape (there is no catch). This exception will propagate to the main script [main], which will stop and display the exception (see the section |The Main Script|). This mechanism has been used for most of the module’s functions;
8.2.4. The [calcul_impôt] function
The [calcul_impôt] function is as follows:
# imports
import codecs
import math
# 2019 tax brackets
limites = [9964, 27519, 73779, 156244, 0]
coeffr = [0, 0.14, 0.3, 0.41, 0.45]
coeffn = [0, 1394.96, 5798, 13913.69, 20163.45]
# constant for 2019 tax calculation
PLAFOND_QF_DEMI_PART = 1551
PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION = 21037
PLAFOND_REVENUS_COUPLE_POUR_REDUCTION = 42074
VALEUR_REDUC_DEMI_PART = 3797
PLAFOND_DECOTE_CELIBATAIRE = 1196
PLAFOND_DECOTE_COUPLE = 1970
PLAFOND_IMPOT_COUPLE_POUR_DECOTE = 2627
PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE = 1595
ABATTEMENT_DIXPOURCENT_MAX = 12502
ABATTEMENT_DIXPOURCENT_MIN = 437
…
# tAX CALCULATION
# ----------------------------------------
def calcul_impôt(marié: str, enfants: int, salaire: int) -> dict:
# married: yes, no
# children: number of children
# salary: annual salary
# limits, coeffr, coeffn: data tables for tax calculation
#
# tax calculation with children
result1 = calcul_impôt_2(marié, enfants, salaire)
impot1 = result1["impôt"]
# tax calculation without children
if enfants != 0:
result2 = calcul_impôt_2(marié, 0, salaire)
impot2 = result2["impôt"]
# application of the family allowance ceiling
if enfants < 3:
# PLAFOND_QF_DEMI_PART euros for the first 2 children
impot2 = impot2 - enfants * PLAFOND_QF_DEMI_PART
else:
# PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
impot2 = impot2 - 2 * PLAFOND_QF_DEMI_PART - (enfants - 2) * 2 * PLAFOND_QF_DEMI_PART
else:
impot2 = impot1
result2 = result1
# we take the highest tax with the rate and surcharge that go with it
if impot1 > impot2:
impot = impot1
taux = result1["taux"]
surcôte = result1["surcôte"]
else:
surcôte = impot2 - impot1 + result2["surcôte"]
impot = impot2
taux = result2["taux"]
# calculation of any discount
décôte = get_décôte(marié, salaire, impot)
impot -= décôte
# calculation of any tax reduction
réduction = get_réduction(marié, salaire, enfants, impot)
impot -= réduction
# result
return {"marié": marié, "enfants": enfants, "salaire": salaire, "impôt": math.floor(impot), "surcôte": surcôte,
"décôte": décôte, "réduction": réduction, "taux": taux}
Notes
- lines 6–8: tax brackets (see section |Calculation of Gross Tax|);
- lines 11–20: constants used in the tax calculation;
- Note that the elements initialized in lines 5–20 will be global to the functions we are about to describe. They are therefore known as long as the function using them does not declare variables with the same names;
- the figures in lines 5–20 change every year. Here, they are the 2019 figures;
- line 25: the function [calcul_impôt] takes three parameters:
- [marié]: yes/no, indicates whether the taxpayer is married or in a civil partnership;
- [enfants]: the number of children;
- [salaire]: their annual salary in euros;
- lines 31–33: tax calculation taking children into account;
- lines 34–47: these lines implement the family quotient cap (see section |Family Quotient Cap|);
- lines 49-57: these lines calculate the taxpayer’s tax rate as well as any surcharge (see section |High-Income Cases|);
- lines 59–61: calculation of any tax credit (see section |Calculation of the tax credit|);
- lines 62–64: calculation of any reduction in the tax due (see section |Calculation of the tax reduction|);
The algorithm is quite complex, and we will not go into more detail than what is provided in the comments. The algorithm implements the tax calculation method as described in the section |The Problem|.
8.2.5. The function [calcul_impôt_2]
The function [calcul_impôt] calls the following function [calcul_impôt_2]:
def calcul_impôt_2(marié: str, enfants: int, salaire: int) -> list:
# married: yes, no
# children: number of children
# salary: annual salary
# limits, coeffr, coeffn: data tables for tax calculation
#
# number of shares
marié = marié.strip().lower()
if marié == "oui":
nb_parts = enfants / 2 + 2
else:
nb_parts = enfants / 2 + 1
# 1 part per child from the 3rd
if enfants >= 3:
# an additional half share for each child from the 3rd onwards
nb_parts += 0.5 * (enfants - 2)
# taxable income
revenu_imposable = get_revenu_imposable(salaire)
# surcharge
surcôte = math.floor(revenu_imposable - 0.9 * salaire)
# for rounding problems
if surcôte < 0:
surcôte = 0
# family quotient
quotient = revenu_imposable / nb_parts
# is set at the end of the limit table to stop the following loop
limites[len(limites) - 1] = quotient
# tAX CALCULATION
i = 0
while quotient > limites[i]:
i += 1
# because we've placed quotient at the end of the limit array, the previous loop
# cannot exceed the limits of the board
# now we can calculate the tax
impôt = math.floor(revenu_imposable * coeffr[i] - nb_parts * coeffn[i])
# result
return {"impôt": impôt, "surcôte": surcôte, "taux": coeffr[i]}
This algorithm was described in section 8.1.1.
8.2.6. The function [get_décôte]
The function [get_décôte] implements the calculation of any tax discount (section |Calculation of the discount|):
# calculates any discount
def get_décôte(marié: str, salaire: int, impots: int) -> int:
# at the outset, a zero discount
décôte = 0
# maximum tax amount to qualify for discount
plafond_impôt_pour_décôte = PLAFOND_IMPOT_COUPLE_POUR_DECOTE if marié == "oui" else PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE
if impots < plafond_impôt_pour_décôte:
# maximum discount
plafond_décôte = PLAFOND_DECOTE_COUPLE if marié == "oui" else PLAFOND_DECOTE_CELIBATAIRE
# theoretical discount
décôte = plafond_décôte - 0.75 * impots
# the discount may not exceed the amount of tax due
if décôte > impots:
décôte = impots
# no discount <0
if décôte < 0:
décôte = 0
# result
return math.ceil(décôte)
8.2.7. The [get_réduction] function
The [get_réduction] function implements the calculation of any reduction in the tax payable (section |Calculation of the tax reduction|):
# calculates any reduction
def get_réduction(marié: str, salaire: int, enfants: int, impots: int) -> int:
# the income ceiling to qualify for the 20% reduction
plafond_revenu_pour_réduction = PLAFOND_REVENUS_COUPLE_POUR_REDUCTION if marié == "oui" else PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION
plafond_revenu_pour_réduction += enfants * VALEUR_REDUC_DEMI_PART
if enfants > 2:
plafond_revenu_pour_réduction += (enfants - 2) * VALEUR_REDUC_DEMI_PART
# taxable income
revenu_imposable = get_revenu_imposable(salaire)
# reduction
réduction = 0
if revenu_imposable < plafond_revenu_pour_réduction:
# 20% discount
réduction = 0.2 * impots
# result
return math.ceil(réduction)
8.2.8. The [get_revenu_imposable] function
The [get_revenu_imposable] function calculates taxable income based on annual salary:
# revenu_imposable = salaireAnnuel - allowance
# the allowance has a minimum and a maximum
# ----------------------------------------
def get_revenu_imposable(salaire: int) -> int:
# 10% salary deduction
abattement = 0.1 * salaire
# this allowance may not exceed ABATTEMENT_DIXPOURCENT_MAX
if abattement > ABATTEMENT_DIXPOURCENT_MAX:
abattement = ABATTEMENT_DIXPOURCENT_MAX
# the allowance cannot be less than ABATTEMENT_DIXPOURCENT_MIN
if abattement < ABATTEMENT_DIXPOURCENT_MIN:
abattement = ABATTEMENT_DIXPOURCENT_MIN
# taxable income
revenu_imposable = salaire - abattement
# result
return math.floor(revenu_imposable)
8.2.9. The [record_results] function
The [record_results] function saves the tax calculation results to a text file:
# writing results to a text file
# ----------------------------------------
def record_results(results_filename: str, results: list):
# results_filename: the name of the text file in which to place the results
# results: the results list in the form of a dictionary list
# each dictionary is written on a line of text
résultats = None
try:
# opening the results file
résultats = codecs.open(results_filename, "w", "utf8")
# taxpayer exploitation
for result in results:
# enter the result in the results file
résultats.write(f"{result}\n")
# next taxpayer
finally:
# close the file if it has been opened
if résultats:
résultats.close()
8.2.10. The results
As previously mentioned, with the following taxpayer file [taxpayersdata.txt]:
The script [main.py] creates the following file [résultats.txt]:
These results match the official figures in the |Official Figures| section.
Now, let’s run this version in a console window:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01>python main.py
Traceback (most recent call last):
File "main.py", line 4, in <module>
from impots.v01.shared.impôts_module_01 import *
ModuleNotFoundError: No module named 'impots'
We encounter an error we’ve seen before: one where a module cannot be found, in this case the module [impots]. As a reminder, this means that:
- the Python interpreter has searched through the Python Path directories one by one;
- in none of them did it find a folder containing a [impots.py] script;
The version [v02] will provide a solution to this problem.