Skip to content

11. Application Exercise: version 3

Image

This new version introduces two changes:

  • the data required to calculate the tax and provided by the tax authorities is placed in a file jSON [admindata.json]:

{
    "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],
    "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
}
  • The results of the tax calculation will also be placed in a jSON [résultats.json] file:

[
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 55555,
    "impôt": 2814,
    "surcôte": 0,
    "décôte": 0,
    "réduction": 0,
    "taux": 0.14
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 50000,
    "impôt": 1384,
    "surcôte": 0,
    "décôte": 384,
    "réduction": 347,
    "taux": 0.14
  },

  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 200000,
    "impôt": 42842,
    "surcôte": 17283,
    "décôte": 0,
    "réduction": 0,
    "taux": 0.41
  }
]

11.1. The [config.py] configuration script

The configuration script will be as follows:


def configure():
    import os
 
    # absolute path of this script's folder
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # application dependencies
    absolute_dependencies = [
        f"{script_dir}/../shared",
    ]
    # application configuration
    config = {
        # absolute path of the taxpayer file
        "taxpayersFilename"f"{script_dir}/../data/taxpayersdata.txt",
        # absolute path of the results file
        "resultsFilename"f"{script_dir}/../data/résultats.json",
        # absolute path of tax administration data file
        "admindataFilename"f"{script_dir}/../data/admindata.json"
    }
    # update syspath
    from myutils import set_syspath
 
    set_syspath(absolute_dependencies)
 
    # we return the config
    return config
  • Line 8: Add the [shared] folder to the Python Path directory. This folder contains the [impôts_module_02] module used by the main script;

11.2. Main script [main.py]

The main script for version 3 is as follows:


# configure the application
import config
 
config = config.configure()
 
# syspath is configured - imports can be made
from impôts_module_02 import calcul_impôt, get_admindata, get_taxpayers_data, record_results_in_json_file
 
# taxpayer file
taxpayers_filename = config['taxpayersFilename']
# results file
results_filename = config['resultsFilename']
# tax administration data file
admindata_filename = config['admindataFilename']
# code
try:
 
    # reading tax administration data
    admindata = get_admindata(admindata_filename)
    # reading taxpayer data
    taxpayers = get_taxpayers_data(taxpayers_filename)
    # results list
    results = []
    # taxpayers' taxes are calculated
    for taxpayer in taxpayers:
        # tax calculation returns a dictionary of keys
        # ['marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
        result = calcul_impôt(admindata, taxpayer['marié'], taxpayer['enfants'], taxpayer['salaire'])
        # the dictionary is added to the list of results
        results.append(result)
    # we record the results
    record_results_in_json_file(results_filename, 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")
finally:
    print("Travail terminé...")

Notes

  • Lines 2–4: We configure the application, specifically its Python module Path;
  • line 7: we import the functions we need into [main.py];
  • lines 9-14: the names of the files used by the application are retrieved from the configuration;
  • The main script in version 3 has three differences compared to those in versions 1 and 2:
  • line 21: tax administration data is retrieved from the jSON and [./data/admindata.json] files;
  • line 32: the tax calculation results are placed in the jSON and [./data/résultats.json] files;
  • line 7: the functions of version 3 are found in module [impots.modules.impôts_module_02];

11.3. The [impots.v02.modules.impôts_module_02] module

The [impots.v02.modules.impôts_module_02] module has the following structure:

Image

  • The module contains functions already present in the module used by version 1, with one difference, however. When the version 2 module reuses a function present in the version 1 module, it does so with an additional parameter: [adminData] (lines 29, 51, 77, 127). This parameter represents the dictionary of tax data from the jSON and [adminData.json] files. In the version 1 module, this data did not need to be passed to the functions because it was globally defined for them, meaning the functions were already aware of it;

11.4. Reading data from the tax administration

The [get_admindata] function is as follows:


# read data from tax authorities in a jSON file
# ----------------------------------------
def get_admindata(admindata_filename: str) -> dict:
    # reading tax administration data
    # let any exceptions show up: file missing, jSON content incorrect
    file = None
    try:
        # open file jSON in read mode
        file = codecs.open(admindata_filename, "r", "utf8")
        # transfer content to a dictionary
        admin_data = json.load(file)
        # we return the result
        return admin_data
    finally:
        # close the file if it has been opened
        if file:
            file.close()
  • line 9: retrieve the image dictionary from the jSON file that was read;

11.5. Saving the results

The function [record_results_in_json_file] does the following:


# writing results to a jSON file
# ----------------------------------------
def record_results_in_json_file(results_filename: str, results: list):
    file = None
    try:
        # opening the results file
        file = codecs.open(results_filename, "w", "utf8")
        # block writing
        json.dump(results, file, ensure_ascii=False)
    finally:
        # close the file if it has been opened
        if file:
            file.close()
  • line 7: create a UTF-8 encoded file;
  • line 9: write the list [results] to the file jSON. UTF-8 characters are not escaped (ensure_ascii=False);

11.6. Function Changes

Some functions now receive an additional [admin_data] parameter. This slightly changes their syntax. Take, for example, the [calcul_impôt] function:


# tax calculation - step 1
# ----------------------------------------
def calcul_impôt(admin_data: dict, 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(admin_data, marié, enfants, salaire)
    impot1 = result1["impôt"]
    # tax calculation without children
    if enfants != 0:
        result2 = calcul_impôt_2(admin_data, 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 * admin_data['plafond_qf_demi_part']
        else:
            # PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
            impot2 = impot2 - 2 * admin_data['plafond_qf_demi_part'] - (enfants - 2) * 2 * admin_data[
                '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(admin_data, marié, salaire, impot)
    impot -= décôte
    # calculation of any tax reduction
    réduction = get_réduction(admin_data, 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

  • Where [calcul_impôt] calls other functions, it passes [admin_data] as the first parameter (lines 10, 14, 39, 42);
  • Where [calcul_impôt] uses tax constants, it now passes through the dictionary [admin_data] (lines 19, 22);

All functions that receive [admin_data] as a parameter undergo these same types of changes.

11.7. Results

The results obtained are those presented at the beginning of section 8.3.