Skip to content

11. Practical Exercise: Version 3

Image

This new version introduces two changes:

  • the data required to calculate the tax, provided by the tax authority, is stored in a JSON file [admindata.json]:

{
    "limits": [9964, 27519, 73779, 156244, 0],
    "coeffR": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffN": [0, 1394.96, 5798, 13913.69, 20163.45],
    "HALF-RATE_INCOME_LIMIT": 1551,
    "INCOME_LIMIT_SINGLE_PERSON_FOR_REDUCTION": 21037,
    "INCOME_LIMIT_FOR_COUPLES_FOR_REDUCTION": 42074,
    "REDUCTION_VALUE_HALF_PORTION": 3797,
    "SINGLE_PERSON_DISCOUNT_LIMIT": 1196,
    "COUPLE_TAX_DEDUCTION_LIMIT": 1970,
    "TAX_THRESHOLD_FOR_COUPLES_FOR_DEDUCTION": 2627,
    "SINGLE_TAX_THRESHOLD_FOR_DEDUCTION": 1595,
    "MAXIMUM_10_PERCENT_DEDUCTION": 12502,
    "MINIMUM_10_PERCENT_DEDUCTION": 437
}
  • The results of the tax calculation will also be placed in a JSON file [results.json]:

[
  {
    "married": "yes",
    "children": 2,
    "salary": 55555,
    "tax": 2814,
    "surcharge": 0,
    "discount": 0,
    "reduction": 0,
    "rate": 0.14
  },
  {
    "married": "yes",
    "children": 2,
    "salary": 50000,
    "tax": 1384,
    "surcharge": 0,
    "discount": 384,
    "reduction": 347,
    "rate": 0.14
  },

  {
    "married": "yes",
    "children": 3,
    "salary": 200000,
    "tax": 42,842,
    "surcharge": 17,283,
    "discount": 0,
    "reduction": 0,
    "rate": 0.41
  }
]

11.1. The configuration script [config.py]

The configuration script will be as follows:


def configure():
    import os

    # absolute path to this script's directory
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # application dependencies
    absolute_dependencies = [
        f"{script_dir}/../shared",
    ]
    # Application configuration
    config = {
        # absolute path to the taxpayers file
        "taxpayersFilename": f"{script_dir}/../data/taxpayersdata.txt",
        # absolute path to the results file
        "resultsFilename": f"{script_dir}/../data/results.json",
        # absolute path to the tax administration data file
        "admindataFilename": f"{script_dir}/../data/admindata.json"
    }
    # update the syspath
    from myutils import set_syspath

    set_syspath(absolute_dependencies)

    # restore the config
    return config
  • Line 8: Add the [shared] folder to the Python Path. 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()

# The syspath is configured—we can now import modules
from tax_module_02 import tax_calculation, get_admin_data, 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:

    # read tax administration data
    admindata = get_admindata(admindata_filename)
    # read taxpayer data
    taxpayers = get_taxpayers_data(taxpayers_filename)
    # list of results
    results = []
    # Calculate taxpayers' taxes
    for taxpayer in taxpayers:
        # the tax calculation returns a dictionary of keys
        # ['married', 'children', 'salary', 'tax', 'surcharge', 'rebate', 'deduction', 'rate']
        result = tax_calculation(admindata, taxpayer['married'], taxpayer['children'], taxpayer['salary'])
        # the dictionary is added to the list of results
        results.append(result)
    # the results are saved
    save_results_in_json_file(results_filename, results)
except BaseException as error:
    # There may be various errors: file not found, incorrect file content
    # display the error and exit the application
    print(f"The following error occurred: {error}]\n")
finally:
    print("Job completed...")

Notes

  • Lines 2–4: We configure the application, specifically its Python 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 authority data is retrieved from the JSON file [./data/admindata.json];
  • line 32: the tax calculation results are placed in the JSON file [./data/results.json];
  • line 7: the functions in version 3 are located in the module [impots.modules.impôts_module_02];

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

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

Image

  • The module contains functions already present in the module used by version 1, with one difference. When the version 2 module reuses a function from 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 file [adminData.json]. 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:


# reading tax administration data from a JSON file
# ----------------------------------------
def get_admindata(admindata_filename: str) -> dict:
    # reading tax authority data
    # allow any exceptions to be raised: file not found, invalid JSON content
    file = None
    try:
        # Open the JSON file for reading
        file = codecs.open(admindata_filename, "r", "utf8")
        # Load the content into a dictionary
        admin_data = json.load(file)
        # return the result
        return admin_data
    finally:
        # Close the file if it was opened
        if file:
            file.close()
  • line 9: retrieve the image dictionary from the read JSON file;

11.5. Saving the results

The [record_results_in_json_file] function is as follows:


# Writing results to a JSON file
# ----------------------------------------
def record_results_in_json_file(results_filename: str, results: list):
    file = None
    try:
        # Open the results file
        file = codecs.open(results_filename, "w", "utf8")
        # write in bulk
        json.dump(results, file, ensure_ascii=False)
    finally:
        # Close the file if it was opened
        if file:
            file.close()
  • line 7: create a UTF-8 encoded file;
  • line 9: write the [results] list to the JSON file. UTF-8 characters are not escaped (ensure_ascii=False);

11.6. Function Modifications

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 calculate_tax(admin_data: dict, married: str, children: int, salary: int) -> dict:
    # married: yes, no
    # children: number of children
    # salary: annual salary
    # limits, coeffr, coeffn: data tables used to calculate the tax
    #
    # tax calculation with children
    result1 = tax_calculation_2(admin_data, married, children, salary)
    tax1 = result1["tax"]
    # Tax calculation without children
    if children != 0:
        result2 = tax_calculation_2(admin_data, married, 0, salary)
        tax2 = result2["tax"]
        # apply the family quotient cap
        if children < 3:
            # HALF-PART FAMILY QUOTIENT CAP in euros for the first 2 children
            tax2 = tax2 - children * admin_data['half-share_family_quota_cap']
        else:
            # HALF-PART_FQ_CAP euros for the first 2 children, double that for subsequent children
            tax2 = tax2 - 2 * admin_data['half-share_limit'] - (children - 2) * 2 * admin_data[
                'half-share_ceiling']
    else:
        tax2 = tax1
        result2 = result1

    # we take the higher tax amount with the corresponding rate and surcharge
    if tax1 > tax2:
        tax = tax1
        rate = result1["rate"]
        surcharge = result1["surcharge"]
    else:
        surcharge = tax2 - tax1 + result2["surcharge"]
        tax = tax2
        rate = result2["rate"]

    # calculate any discount
    discount = get_discount(admin_data, married, salary, tax)
    tax -= discount
    # calculate any tax reduction
    tax_credit = get_tax_credit(admin_data, married, salary, children, tax)
    tax -= reduction
    # result
    return {"married": married, "children": children, "salary": salary, "tax": math.floor(tax), "surcharge": surcharge,
            "discount": discount, "reduction": reduction, "rate": rate}

Notes

  • where [calcul_tax] calls other functions, it passes [admin_data] as the first parameter (lines 10, 14, 39, 42);
  • where [tax_calculation] uses tax constants, it now accesses them via the [admin_data] dictionary (lines 19, 22);

All functions receiving [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.