Skip to content

4. Solving the three problems with Google Gemini

We will provide screenshots of the three Gemini sessions that were used to solve the three problems posed. We will go into considerable detail. Once this is done, we will not repeat the process for the other AIs tested. They operate in a similar manner. We will only provide the most notable details.

4.1. Introduction

We refer back to the first Gemini screenshot provided earlier:

 
  • In [1], the Gemini URL;
  • In [2], the version of Gemini used;
  • In [3-5], the three problems posed to Gemini;

Gemini is a Google product available at the URL [https://gemini.google.com/]. To view a history of your question-and-answer sessions as shown above, you must create an account. Furthermore, like all the other AIs tested, Gemini limits the number of questions you can ask and the number of files you can upload. When this limit is reached, the session ends, and you’re offered the option to continue it later. Since it’s quite frustrating to stop in the middle of a session, I signed up for a subscription. Fortunately, the first month of the Gemini subscription is free. I did the same with the other AIs that had these limits, namely ChatGPT, MistralAI, and ClaudeAI. I signed up for a one-month subscription, but in those cases, the first month was paid. I didn’t encounter any limits with Grok. DeepSeek doesn’t announce any limits but sometimes responds with [Server busy] and interrupts the session. That’s essentially setting limits without saying so.

From here on, I’ll refer to question-and-answer sessions simply as “sessions.” AIs most often use the English term “chat” or “conversation.”

Gemini’s interface for asking a question is as follows:

  • In [1], your question;
  • In [2], the icon that launches the AI to calculate the response;
  • In [3-4], you can attach files;

4.2. Problem 1

The session for Problem 1 is as follows:

 
  • In [1], the question;
  • In [2], the beginning of Gemini’s answer;

The rest of the answer is as follows:

 
 
 
 

The answer is correct. The other five AIs will also give the correct answer in a similar form.

4.3. Problem 2

4.3.1. Introduction

Here we recall the initial problem from the [python3-flask-2020] course. This is a text given to students in a tutorial.

 

The table above allows us to calculate the tax in the simplified case of a taxpayer who has only their salary to declare. As indicated in note (1), the tax calculated in this way is the tax before three mechanisms:

  • The capping of the family quotient, which applies to high incomes;
  • The tax credit and tax reduction that apply to low-income earners;

Thus, the tax calculation involves 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 liability for 2019 in the simplified case of a taxpayer who has only their salary to report.

4.3.1.1. Calculation of Gross Tax

Gross tax can be calculated as follows:

First, 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+nbChildren*0.5+(nbChildren-2)*0.5 if the employee is unmarried;
  • nbParts=2+nbChildren*0.5+(nbChildren-2)*0.5 if they are married;
  • where nbChildren 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 QF = 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 row where QF <= field1 is as follows:

    27,519    0.14    1394.96

Tax I is then equal to 0.14*R – 1394.96*numberOfShares=[0.14*45000-1394.96*3]=2115. The tax is rounded down to the nearest euro.

If the condition QF <= field1 holds on the first line, then the tax is zero.

If QF is such that the condition QF <= field1 is never satisfied, then the coefficients from the last line are used. Here:

    0    0.45    20,163.45

which gives the gross tax I = 0.45*R – 20163.45*nbParts.

4.3.1.2. Family Quotient Cap

 

To determine whether the family quotient (QF) cap 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:

    27519    0.14    1394.96

Tax I is then equal to 0.14*R – 1394.96*number of shares = [0.14*45,000 – 1394.96*2] = 3,510.

Maximum child-related benefit: 1551 * 2 = 3102 euros

Minimum tax: 3,510 – 3,102 = 408 euros

The gross tax with 2 shares, already calculated in the previous paragraph (2,115 euros), is greater than the minimum tax (408 euros), so the family cap does not apply here.

In general, the gross tax is greater than (tax1, tax2) where:

  • [tax1]: is the gross tax calculated including children;
  • [tax2]: is the gross tax calculated without children and reduced by the maximum credit (here 1,551 euros per half-share) related to children;

4.3.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 euros) 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:

tax credit = 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

Two rules must be observed when calculating the discount (some AI tools have stumbled on this issue):

  • The discount cannot be negative;
  • The discount cannot exceed the tax already calculated;

4.3.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: 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;

4.3.1.5. Calculation of net tax

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.

4.3.1.6. High-Income Cases

Our previous example applies to the majority of employees. However, the tax calculation differs for high-income earners.

4.3.1.6.1. Cap on the 10% reduction on 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;

4.3.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 100,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;
  • His family quotient is therefore QF = R / nbParts = 90,000 / 4 = 22,500 euros;
  • His 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 derived from 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;
  • Since I3 > I1, tax I3 will be retained;

This couple will receive neither a tax credit nor a reduction, and their final tax will be 9,200 euros.

4.3.1.7. Official figures

Tax calculation is complex. Throughout this 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 euros
Tax rate = 14%
Couple with 2 children and an annual income of 50,000 euros
Tax = 1,385 euros
Tax credit = €384
Reduction = 346 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
Tax credit = 720 euros
Reduction = 0 euros
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
Reduction = 0 euros
Tax rate = 41%
Single with 3 children and an annual income of 100,000 euros
Tax = €16,782
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 euros
Surcharge = 2,180 euros
Discount = 0 euros
Reduction = 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, no children, and 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
Reduction=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 allowance;

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;
  • Down to the nearest euro for surcharges and the final tax;

We will ask the AI to perform this tax calculation.

4.3.2. Gemini Session Configuration

The question posed to Gemini is accompanied by two files:

 
  • In [1], the calculation just detailed has been put into a PDF that is provided to Gemini. Gemini will find there the exact rules for the simplified calculation of the 2019 tax on 2018 income;
  • In [2], our instructions;
  • In [3], the command to launch the AI;

Our instructions in the text file [instructionsAvecPDF.txt] are as follows:

1 - Use French.

2 - Can you generate a Python script to calculate the tax paid by families in 2019 on their 2018 income?

3 - Use the PDF document I have attached, which explains the calculations to be performed.

4 - You must pay attention to the following points:

- Cap on the family quotient. There are thresholds to check.
- Calculation of the tax reduction in certain cases. There are thresholds to check.
- Calculation of the 20% reduction in certain cases. There are thresholds to check.
- Cap on the 10% deduction from annual income in certain cases.
- You should treat all income as reportable for Taxpayer 1, even if the couple is married.

5 - You will add unit tests to the generated script for the following cases.

In these tests, we call:

adults: number of adults in the tax household
children: number of children in the tax household
income: annual net income before taxes, i.e., before the first calculation of the tax deduction.
tax: the tax due
discount: any discount applicable to the tax household
reduction: the 20% reduction for low-income earners

Here are the 11 tests to verify. They have all been manually verified using the official
for the 2019 tax calculation [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.html].
If you use this simulator, the income must be attributed solely to filer 1 in the case of a couple; filer 2 is then ignored. When income is split
between two filers, the result is different.

We use the syntax (adults, children, income) -> (tax, discount, reduction) to indicate that the script receives the inputs
(adults, children, income) and produces the results (tax, discount, reduction)

test1: (2,2,55555) -> (2815, 0, 0)
test2: (2, 2, 50000) -> (1385, 384, 346)
test3: (2, 3, 50000) -> (0, 720, 0)
test4: (1,2,100000) -> (19884, 0, 0)
test5: (1, 3, 100000) -> (16782, 0, 0)
test6: (2, 3, 100000) -> (9200, 0, 0)
test7: (2, 5, 100000) -> (4230, 0, 0)
test8: (1, 0, 100000) -> (22986, 0, 0)
test9: (2, 2, 30000) -> (0, 0, 0)
test10: (1, 0, 200000) -> (64211, 0, 0)
test11: (2, 3, 200000) -> (42843, 0, 0)

6 - Rounding issues may arise. Proceed as follows
- the tax due will be rounded down to the nearest euro,
- the discount will be rounded up to the nearest euro,
- the 20% reduction will be rounded up to the nearest euro.
- the 10% deduction will be rounded up to the nearest euro

Perform all unit tests to the nearest euro due to these potential rounding errors.
Dont try to get the exact values listed above; instead, aim for values within 1 euro of them.

7 - Avoid searching online. The PDF Im giving you is correct.
Only submit your result once you have successfully passed all 11 unit tests.

8 - If one of the tests fails and you get stuck, post your reasoning for that test
so I can help you.

9 - Include detailed comments in the script you generate.
Put the progressive scale into a list or dictionary, then use that list or dictionary
Hard-code the numbers (magic numbers) you use into constants
Use functions to separate the steps of the calculation.
Write everything in French

9 - Do not display the generated code on the screen. Just give me a link to retrieve it.
If the unit tests fail, Ill give you the script execution logs so
you can see your errors.

10 - If possible, indicate the time in minutes and seconds it took you to produce
the requested script.

These instructions are the result of numerous questions asked of Gemini. It quickly becomes clear that the AI needs to be very tightly guided if we want to get what we want. It was because of all this trial and error that the Gemini session was ultimately terminated for exceeding limits. Let’s examine the rest of these instructions:

  • Line 1: We specify that the conversation should be in French. This instruction is for DeepSeek, which tended to speak English;
  • Line 3: what we want;
  • Line 5: We tell the AI to use the PDF we provided;
  • Lines 7–14: a number of useful tips, especially for Problem 3 without the PDF. Several AIs got lost in the tax calculation;
  • Lines 15–44: the 11 unit tests we want included in the generated script. Once the script is generated, we’ll run it in PyCharm and see if all 11 tests pass;
  • Lines 46–53: Without these instructions, the AIs would generate unit tests seeking exact results that would fail;
  • Lines 55–56: I tell the AI not to go online. The simplest solution is to use the PDF;
  • Lines 58–59: The AI did not follow this instruction. I had to explicitly write it in a prompt when I noticed that a test had failed;
  • Lines 61–65: I specify what type of Python script I want;
  • Lines 67–69: I would have preferred a link to retrieve the generated script because displaying the code on screen takes time. It turned out that most AIs cannot do this. The links provided did not work;
  • Lines 71–72: I would have liked to know the time the AI took to answer the question. Only Gemini was able to provide this information. The other AIs either did not respond to this instruction or provided arbitrary numbers, indicating they did not understand it;

4.3.3. Gemini’s response

Gemini’s first response is as follows:

 
  • In [1-4], Gemini provides links to the part of the PDF or text file containing the instructions it is using at a given moment;

The rest is as follows:

 
  • In [1], Gemini states that it successfully executed all 11 unit tests. Most AIs made this claim for both Problem 2 and Problem 3, and often when the generated script was loaded, it did not work. This claim should therefore be taken with a grain of salt. For Gemini, however, this will prove to be true;
  • In [2], a link that turns out not to work;
  • In [3], only Gemini provided a realistic execution time;

So the link [2] does not work. We tell Gemini:

 

Gemini’s response:

 
  • In [1], the Python script generated by Gemini;

We load this script into PyCharm and run it:

 
  • In [1], [gemini1] is the script generated by Gemini;

When the script is run, the following compilation errors appear:

"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py" 
Testing started at 17:12 ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours

Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm 2025.2.0.1\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py", line 38, in <module>
    sys.exit(main(argv=args, module=None, testRunner=unittestpy.TeamcityTestRunner,
             ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  buffer=not JB_DISABLE_BUFFERING))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 103, in __init__
    self.parseArgs(argv)
    ~~~~~~~~~~~~~~^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 142, in parseArgs
    self.createTests()
    ~~~~~~~~~~~~~~~~^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 153, in createTests
    self.test = self.testLoader.loadTestsFromNames(self.testNames,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
                                                   self.module)
                                                   ^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\loader.py", line 207, in loadTestsFromNames
    tests = [self.loadTestsFromName(name, module) for name in names]
              ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\loader.py", line 137, in loadTestsFromName
    module = __import__(module_name)
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py", line 28, in <module>
    [cite_start]
     ^^^^^^^^^^
NameError: name 'cite_start' is not defined

Process finished with exit code 1

Empty suite
  • Line 30, the compilation error. [cite_start] is a marker used to generate a specific type of text;

We put the logs above into a file [logs gemini1.txt] and give it to Gemini:

 

Gemini’s response is then as follows:

 

When loaded into PyCharm, running the new script generates exactly the same error. We tell Gemini this by providing the execution logs again:

 

Gemini’s response is as follows:

 

This time it works. All 11 unit tests pass. We tell Gemini:

 

To which it replies:

 

The script generated by Gemini followed the instructions given in the text file [instructionsWithPDF.txt]:

# -*- coding: utf-8 -*-
"""
Script for calculating 2019 income tax on 2018 income in France,
based on the simplified rules provided in the PDF document.
"""

import math
import unittest

# --- CALCULATION CONSTANTS FOR 2019 TAX ---
# All values are taken from the provided PDF document.

# Thresholds for the 10% deduction on annual income
10_PERCENT_DEDUCTION_THRESHOLD = 12502.0
MINIMUM_10_PERCENT_DEDUCTION = 437.0

# 2019 Income Tax Bracket
# Structure: (Maximum threshold for the bracket, Tax rate, Amount to be deducted per share)
2019_TAX_BRACKET = [
    {"threshold": 9964, "rate": 0.0, "deduction_per_share": 0},
    {"threshold": 27519, "rate": 0.14, "deduction_per_bracket": 1394.96},
    {"threshold": 73779, "rate": 0.30, "deduction_per_share": 5798},
    {"threshold": 156244, "rate": 0.41, "deduction_per_share": 13913.69},
    {"threshold": float('inf'), "rate": 0.45, "deduction_per_share": 20163.45}
]

# Family quotient cap
MAX_GAIN_PER_HALF_SHARE = 1551.0  # Maximum gain per additional half-share

# Thresholds and amounts for the reduction
THRESHOLD_FOR_DISCOUNT_APPLICATION = {"single": 1595.0, "couple": 2627.0}
DISCOUNT_CALC_THRESHOLD = {"single": 1196.0, "couple": 1970.0}

# Thresholds for the 20% tax reduction
THRESHOLD_BASIC_REDUCTION_20 = {"single": 21037.0, "couple": 42074.0}
INCREMENT_THRESHOLD_REDUCTION_PER_HALF_SHARE = 3797.0


def calculate_number_of_shares(adults: int, children: int) -> float:
    """
    Calculates the number of tax shares for the household.
    - Each parent contributes 1 share.
    - The first two children each contribute 1/2 share.
    - Subsequent children each contribute one share.
    """
    if adults not in [1, 2]:
        raise ValueError("The number of adults must be 1 or 2.")

    parts = float(adults)
    if children > 0:
        portions += 0.5 * min(children, 2)
    if children > 2:
        shares += 1.0 * (children - 2)

    return shares


def calculate_taxable_income(income: float) -> float:
    """
    Calculates the net taxable income after the 10% deduction.
    This deduction is capped.
    The deduction is rounded up to the nearest euro.
    """
    deduction_calculated = income * 0.1
    # The deduction is capped between a minimum and a maximum
    allowance = min(10_PERCENT_ALLOWANCE_CAP, max(10_PERCENT_ALLOWANCE_MINIMUM, calculated_allowance))

    # The problem asks to round the deduction up to the nearest euro
    rounded_deduction = math.ceil(deduction)

    return income - rounded_deduction


def calculate_gross_tax_base(taxable_income: float, number_of_shares: float) -> float:
    """
    Calculates the gross tax using the progressive tax scale.
    """
    if number_shares <= 0:
        return 0.0

    family_quotient = taxable_income / number_of_shares

    applicable_bracket = None
    for bracket in TAX_BRACKETS_2019:
        if family_quota <= bracket["threshold"]:
            applicable_bracket = bracket
            break

    # If the family quotient is greater than all thresholds, use the last bracket
    if applicable_bracket is None:
        applicable_bracket = TAX_SCALE_2019[-1]

    # Formula for calculating gross tax
    gross_tax = (taxable_income * applicable_bracket["rate"]) - (
            applicable_bracket["deduction_per_share"] * number_of_shares)

    return max(0.0, gross_tax)


def apply_QF_cap(gross_tax_with_children: float, taxable_income: float, adults: int,
                              children: int) -> float:
    """
    Applies the family quotient cap if necessary.
    The final tax cannot be less than the tax calculated without children,
    minus a maximum gain per half-share.
    """
    if children == 0:
        return gross_tax_with_children

    # Calculate tax without the children's shares
    shares_without_children = calculate_number_of_shares(adults, 0)
    tax_without_children = calculate_base_gross_tax(taxable_income, shares_without_children)

    # Calculate the maximum child-related allowance
    shares_with_children = calculate_number_of_shares(adults, children)
    half_shares_with_children = (shares_with_children - shares_without_children) * 2
    maximum_income = half_child_allowance * MAX_HALF_ALLOWANCE

    minimum_tax_cap = tax_without_children - maximum_income

    # The tax withheld is the higher of the two calculations
    return max(gross_tax_with_children, minimum_tax_ceiling)


def apply_discount(tax_before_discount: float, adults: int) -> tuple[float, float]:
    """
    Applies the discount if the tax is below certain thresholds.
    The discount is rounded up to the nearest euro.
    """
    status = "couple" if adults == 2 else "single"
    application_threshold = APPLICATION_THRESHOLD_DISCOUNT[status]
    calculation_threshold = CALCULATION_THRESHOLD_DISCOUNT[status]

    discount = 0.0
    # The discount applies if the tax is below the application threshold
    if tax_before_discount < application_threshold:
        # Discount formula: threshold - 0.75 * Gross tax
        calculated_discount = calculation_threshold - (0.75 * tax_before_discount)

        # Round up to the nearest euro
        discount = math.ceil(calculated_discount)

        # The discount cannot be negative or greater than the tax
        discount = max(0.0, min(discount, tax_before_discount))

    tax_after_discount = tax_before_discount - discount
    return tax_after_discount, discount


def apply_20_percent_reduction(tax_before_reduction: float, taxable_income: float, adults: int,
                                    children: int) -> tuple[float, float]:
    """
    Applies the 20% reduction based on income.
    The reduction is rounded up to the nearest euro.
    """
    status = "couple" if adults == 2 else "single"
    base_threshold = BASE_THRESHOLD_20_DISCOUNT[status]

    # The threshold is increased for each half-share associated with children
    shares_with_children = calculate_number_of_shares(adults, children)
    adult_shares = calculate_number_of_shares(adults, 0)
    child_half_shares = (shares_with_children - adult_shares) * 2
    final_threshold = base_threshold + (child_half_shares * THRESHOLD_INCREASE_PER_HALF_SHARE)

    reduction = 0.0
    # The reduction applies if taxable income is below the threshold
    if taxable_income < final_threshold:
        calculated_reduction = tax_before_reduction * 0.20

        # Round up to the nearest euro
        reduction = math.ceil(calculated_reduction)

    tax_after_reduction = tax_before_reduction - reduction
    return max(0.0, tax_after_reduction), reduction


def calculate_tax(adults: int, income: float, children: int) -> dict:
    """
    Performs the complete calculation of the net tax payable.
    """
    # Step 1: Calculate taxable income
    taxable_income = calculate_taxable_income(income)

    # Step 2: Calculate the number of shares
    number_of_brackets = calculate_number_of_brackets(adults, children)

    # Step 3: Calculate gross tax
    initial_gross_tax = calculate_base_gross_tax(taxable_income, number_of_brackets)

    # Step 4: Apply the QF cap
    tax_after_cap = apply_QF_cap(initial_gross_tax, taxable_income, adults, children)

    # Step 5: Apply the discount
    tax_after_discount, calculated_discount = apply_discount(tax_after_cap, adults)

    # Step 6: Apply the 20% reduction
    tax_before_final_rounding, calculated_reduction = apply_20_percent_reduction(tax_after_discount,
                                                                                    taxable_income, adults, children)

    # Step 7: Round the final tax amount down to the nearest euro
    final_tax = math.floor(tax_before_final_rounding)

    return {
        "tax": final_tax,
        "discount": calculated_discount,
        "reduction": calculated_reduction
    }


# --- UNIT TESTS ---
# Validating the script with the provided test cases
class TestCalculImpot2019(unittest.TestCase):

    def _verify_result(self, adults, children, income, expected_tax, expected_discount, expected_reduction):
        """Utility function to run a test and verify the results to within 1 euro."""
        result = calculate_tax(adults=adults, income=income, children=children)

        self.assertAlmostEqual(result["tax"], expected_tax, delta=1,
                               msg=f"Test ({adults}, {children}, {income}): Expected tax {expected_tax}, actual {result['tax']}")

        self.assertAlmostEqual(result["discount"], expected_discount, delta=1,
                               msg=f"Test ({adults}, {children}, {income}): Expected discount {expected_discount}, obtained {result['discount']}")

        self.assertAlmostEqual(result["reduction"], expected_reduction, delta=1,
                               msg=f"Test ({adults}, {children}, {income}): Expected discount {expected_discount}, obtained {result['discount']}")

    def test_case_1(self):
        # (2, 2, 55555) -> (2815, 0, 0)
        self._verify_result(2, 2, 55555, 2815, 0, 0)

    def test_case_2(self):
        # (2, 2, 50000) -> (1385, 384, 346)
        self._verify_result(2, 2, 50000, 1385, 384, 346)

    def test_case_3(self):
        # (2, 3, 50000) -> (0, 720, 0)
        self._verify_result(2, 3, 50000, 0, 720, 0)

    def test_case_4(self):
        # (1, 2, 100000) -> (19884, 0, 0)
        self._verify_result(1, 2, 100000, 19884, 0, 0)

    def test_case_5(self):
        # (1, 3, 100000) -> (16782, 0, 0)
        self._verify_result(1, 3, 100000, 16782, 0, 0)

    def test_case_6(self):
        # (2, 3, 100000) -> (9200, 0, 0)
        self._verify_result(2, 3, 100000, 9200, 0, 0)

    def test_case_7(self):
        # (2, 5, 100000) -> (4230, 0, 0)
        self._verify_result(2, 5, 100000, 4230, 0, 0)

    def test_case_8(self):
        # (1, 0, 100000) -> (22986, 0, 0)
        self._verify_result(1, 0, 100000, 22986, 0, 0)

    def test_case_9(self):
        # (2, 2, 30000) -> (0, 0, 0)
        self._verify_result(2, 2, 30000, 0, 0, 0)

    def test_case_10(self):
        # (1, 0, 200000) -> (64211, 0, 0)
        self._verify_result(1, 0, 200000, 64211, 0, 0)

    def test_case_11(self):
        # (2, 3, 200000) -> (42843, 0, 0)
        self._verify_result(2, 3, 200000, 42843, 0, 0)


if __name__ == '__main__':
    print("Running unit tests...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

    # Example of using the calculator for a specific case
    print("\n--- Calculation example ---")
    annual_income = 50000
    number_of_adults = 2
    number_of_children = 2

    calculation_result = calculate_tax(adults=number_of_adults, income=annual_income, children=number_of_children)

    print(f"For a couple ({number_of_adults} adults) with {number_of_children} children and {annual_income}€ in income:")
    print(f"  - Tax due: {resultat_calcul['impot']}€")
    print(f"  - Discount amount: {resultat_calcul['decote']}€")
    print(f"  - Discount amount: {resultat_calcul['reduction']}€")

I haven’t verified this code. Since the 11 unit tests passed, I consider it “probably correct.” I haven’t done anything more for my own code than verifying these 11 tests.

4.4. Problem 3

Problem 3 is identical to Problem 2, except that we no longer provide the AI with the PDF containing the calculation rules to follow.

The initial question to Gemini is as follows:

 

The instructions file in [1] is almost the same as for Problem 2, with the following differences:


1 - Express yourself in French.

2 - Can you generate a Python script to calculate the tax paid by families in 2019 on their 2018 income?

3 - You will use sources you find on the internet. In your answer, please list these sources.

4 - You must pay attention to the following points:

  • In [3], the student is asked to find the rules for calculating 2019 tax on 2018 income online. This is a more difficult exercise than the previous one;

Below, I am providing only parts of Gemini’s first answer:

 
 

The estimated time is plausible. We wait a long time for Gemini’s response.

As before, Gemini provided a download link for the generated script, but the link does not work. We tell him:

 

Gemini’s response:

 

We load the script into PyCharm under the name [gemini2]:

 

We run it and… it doesn’t work. The execution logs are as follows:


"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py" 
Testing started at 5:23 PM ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours


Failure
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 278, in test_cas_2
    self.assertAlmostEqual(import, 1385, delta=1)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1691 != 1385 within 1 delta (306 difference)


Error
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 291, in test_cas_3
    tax, _, _ = calculate_final_tax(2, 3, 50000)
                  ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculate_final_tax
    discount, tax_after_discount = calculate_discount(tax_after_capping, adults)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculate_discount
    discount = discount_threshold - (tax_before_discount * DISCOUNT_COEFFICIENT)
                                                  ^^^^^^^^^^^^^^^^^
NameError: name 'COFFICIENT_DECOTE' is not defined. Did you mean: 'COEFFICIENT_DECOTE'?


Error
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 316, in test_cas_9
    self._verifier_cas(2, 2, 30000, (0, 0, 0))
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 216, in _verifier_cas
    calculated_tax, calculated_discount, calculated_reduction = calculate_final_tax(adults, children, income)
                                                         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculate_final_tax
    discount, tax_after_discount = calculate_discount(tax_after_capping, adults)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculate_discount
    discount = discount_threshold - (tax_before_discount * DISCOUNT_COEFFICIENT)
                                                  ^^^^^^^^^^^^^^^^^
NameError: name 'COFFICIENT_DECOTE' is not defined. Did you mean: 'COEFFICIENT_DECOTE'?



Ran 11 tests in 0.038s

FAILED (failures=1, errors=2)

Process finished with exit code 1
  • Line 11, a test failed;
  • Lines 25 and 42: the same compilation error;

We put these logs into a text file that we give to Gemini:

 

Gemini’s response:

 
 

We load the script into PyCharm and run it. More errors. We tell Gemini, attaching the execution logs again:

 

Gemini’s response:

 
 
 

We load this new script into PyCharm and run it. This time, all 11 unit tests pass:

The code generated by Gemini is as follows:

# -*- coding: utf-8 -*-
import math
import unittest

# ==============================================================================
# CONSTANTS FOR 2018 INCOME TAX (PAID IN 2019)
# ==============================================================================
INCOME_TAX_BRACKETS_2019 = [
    {'threshold': 0, 'rate': 0.0},
    {'threshold': 9964, 'rate': 0.14},
    {'threshold': 27519, 'rate': 0.30},
    {'threshold': 73779, 'rate': 0.41},
    {'threshold': 156244, 'rate': 0.45},
]
FIXED_DEDUCTION_RATE = 0.10
MINIMUM_FLAT_RATE_DEDUCTION = 437
FLAT_DEDUCTION_CAP = 12502
CAP_ON_HALF-SHARE_QF_BENEFIT = 1551
SINGLE_PERSON_DISCOUNT_THRESHOLD = 1196
COUPLE_DISCOUNT_THRESHOLD = 1970
DISCOUNT_COEFFICIENT = 0.75
20_PERCENT_REDUCTION_RATE = 0.20
THRESHOLD_FOR_SINGLE_PERSON_REDUCTION = 18985
COUPLE_REDUCTION_THRESHOLD = 37970
INCREASE_FOR_HALF_PORTION_REDUCTION = 3803


# ==============================================================================
# CALCULATION FUNCTIONS
# ==============================================================================

def calculate_number_of_shares(adults: int, children: int) -> float:
    """Calculates the number of shares for the tax household."""
    if adults < 1: return 0
    shares = float(adults)
    if children >= 1: shares += 0.5
    if children >= 2: shares += 0.5
    if children >= 3: shares += (children - 2) * 1.0
    return shares


def calculate_taxable_income(gross_income: float) -> float:
    """Calculates the net taxable income after a 10% deduction."""
    if gross_income == 0: return 0.0
    allowance = gross_income * FLAT_ALLOWANCE_RATE
    if deduction < MINIMUM_FLAT_DEDUCTION: deduction = MINIMUM_FLAT_DEDUCTION
    if deduction > FLAT_DEDUCTION_CAP: deduction = FLAT_DEDUCTION_CAP
    allowance = math.ceil(allowance)
    taxable_income = gross_income - deduction
    return taxable_income if taxable_income > 0 else 0


def calculate_gross_tax_per_bracket(taxable_income: float, number_of_shares: float) -> float:
    """Calculates the gross tax by applying the progressive tax scale."""
    if number_of_shares <= 0 or taxable_income <= 0: return 0.0
    family_quotient = taxable_income / number_of_shares
    tax_per_share = 0.0
    for i in range(len(TAX_BRACKETS_2019)):
        bracket = TAX_SCALE_2019[i]
        current_threshold = bracket['threshold']
        rate = bracket['rate']
        if family_quota > current_threshold:
            taxable_base_for_bracket = 0
            if i < len(TAX_BRACKET_2019) - 1:
                next_threshold = TAX_BRACKET_2019[i + 1]['threshold']
                tax_base_for_bracket = min(family_quota, next_threshold) - current_threshold
            else:
                tax_base_for_bracket = family_allowance - current_threshold
            tax_per_share += taxable_base_for_bracket * rate
    return tax_per_share * number_of_shares


def apply_tax_cap(taxable_income: float, number_of_shares: float, adults: int,
                              family_gross_tax: float) -> float:
    """Applies the cap on the effects of the family quotient."""
    if number_of_shares <= adults: return gross_family_tax
    base_tax = calculate_gross_tax_per_bracket(taxable_income, float(adults))
    half_child_shares = (number_of_shares - adults) * 2
    max_family_quota_benefit = CAP_ON_HALF-SHARE_FAMILY_QUOTA_BENEFIT * half_shares_for_children
    actual_QF_benefit = base_tax - gross_family_tax
    if actual_QF_benefit > max_QF_benefit:
        return base_tax - max_QF_benefit
    return family_gross_tax


def calculate_final_tax(adults: int, children: int, income: float) -> (int, int, int):
    """Performs the complete tax calculation."""
    taxable_income = calculate_taxable_income(income)
    number_of_brackets = calculate_number_of_brackets(adults, children)
    gross_tax = calculate_gross_tax_by_bracket(taxable_income, number_of_brackets)
    tax_after_cap = apply_qf_cap(taxable_income, number_of_brackets, adults, gross_tax)

    # CORRECTION: If the tax is already 0, no discount or reduction.
    if tax_after_cap == 0:
        return 0, 0, 0

    # --- CORRECTED LOGIC FOR DISCOUNT/REDUCTION INTERACTION ---
    calculated_discount = 0.0
    calculated_reduction = 0.0
    tax_to_process = tax_after_cap

    # 1. Determine eligibility for the 20% reduction
    is_eligible_for_reduction = False
    if adults >= 2:
        threshold_rfr = THRESHOLD_RFR_REDUCTION_COUPLE + max(0, (number_of_shares - 2) * 2) * INCREASE_RFR_REDUCTION_PER_HALF_SHARE
    else:
        threshold_rfr = THRESHOLD_RFR_DISCOUNT_SINGLE + max(0, (
                number_of_shares - 1) * 2) * RFR_REDUCTION_INCREASE_PER_HALF_SHARE
    if taxable_income < rfr_threshold:
        is_eligible_for_reduction = True

    # 2. Determine eligibility for the tax credit
    household_discount_threshold = COUPLE_DISCOUNT_THRESHOLD if adults >= 2 else SINGLE_PERSON_DISCOUNT_THRESHOLD
    theoretical_reduction = math.ceil(tax_to_be_processed * 20_PERCENT_REDUCTION_RATE) if eligible_for_reduction else 0

    if (tax_to_be_processed - theoretical_reduction) < household_discount_threshold:
        calculated_discount = household_discount_threshold - (tax_to_be_processed * DISCOUNT_COEFFICIENT)
        calculated_discount = math.ceil(calculated_discount)
        # CORRECTION: The discount returned cannot exceed the tax it offsets
        calculated_discount = min(calculated_discount, math.ceil(tax_to_be_processed))
        tax_to_be_processed -= calculated_discount
        tax_to_be_processed = max(0.0, tax_to_be_processed)

    # 3. Apply the 20% reduction if eligible
    if eligible_for_reduction:
        calculated_discount = math.ceil(tax_to_be_processed * 20_PERCENT_DISCOUNT_RATE)
        tax_to_be_paid -= calculated_discount
        tax_to_be_processed = max(0.0, tax_to_be_processed)

    final_tax = math.floor(tax_to_be_processed)

    return final_tax, int(calculated_discount), int(calculated_reduction)


# ==============================================================================
# UNIT TESTS
# ==============================================================================

class TestCalculImpot2019(unittest.TestCase):
    def _verify_case(self, adults, children, income, expected):
        expected_tax, expected_discount, expected_reduction = expected
        calculated_tax, calculated_discount, calculated_reduction = calculate_final_tax(adults, children, income)
        self.assertAlmostEqual(calculated_tax, expected_tax, delta=1, msg=f"Tax ({calculated_tax} vs {expected_tax})")
        self.assertAlmostEqual(calculated_discount, expected_discount, delta=1,
                               msg=f"Discount ({calculated_discount} vs {expected_discount})")
        self.assertAlmostEqual(calculated_discount, expected_discount, delta=1,
                               msg=f"Discount ({calculated_discount} vs {expected_discount})")

    def test_case_1(self): self._verify_case(2, 2, 55555, (2815, 0, 0))

    def test_case_2(self): self._verify_case(2, 2, 50000, (1385, 384, 346))

    def test_case_3(self): self._verify_case(2, 3, 50000, (0, 720, 0))

    def test_case_4(self): self._verify_case(1, 2, 100000, (19884, 0, 0))

    def test_case_5(self): self._verify_case(1, 3, 100000, (16782, 0, 0))

    def test_case_6(self): self._verify_case(2, 3, 100000, (9200, 0, 0))

    def test_case_7(self): self._verify_case(2, 5, 100000, (4230, 0, 0))

    def test_case_8(self): self._verify_case(1, 0, 100000, (22986, 0, 0))

    def test_case_9(self): self._verify_case(2, 2, 30000, (0, 0, 0))

    def test_case_10(self): self._verify_case(1, 0, 200000, (64211, 0, 0))

    def test_case_11(self): self._verify_case(2, 3, 200000, (42843, 0, 0))


if __name__ == '__main__':
    print("Running unit tests for the 2019 tax calculation...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

Again, I haven’t inspected this code. I simply noted that it passed all 11 tests successfully.

But one might be curious to know its reasoning, particularly for specific cases in tax calculation. Let’s ask it:

 

This is a high-income case with both a possible 10% deduction cap and a possible family quotient cap.

Gemini’s response is as follows:

 
 
 
 
 

These last two screenshots are interesting. Gemini uses a calculation method different from the one explained in the PDF. This calculation method can indeed be found online. The two methods are equivalent.

 
 
 

The explanation is remarkably clear. It could be given as is to students to explain the tax calculation method.

Now let’s take another example, this time with low income. In this case, there may be a tax credit and a reduction:

 

Gemini’s response is as follows:

 
 
 
 
 

Here, we see that Gemini applies a rule that is not in the PDF. He probably found it online, but is the source reliable?

 

Here, Gemini continues to apply an unknown rule (the special rule mentioned above).

 
 
 

So Gemini’s results match those of the official tax simulator. But it used a rule not found in the PDF. Where is the error? We ask Gemini, attaching the PDF:

 

Gemini’s response:

 
 
 
 

I think Gemini is right and that my PDF is incorrect. To verify this, I ask him to run a test:

  • Where his reasoning would yield the same results as the official tax simulator;
  • Where the reasoning in the PDF would give results different from those of the simulator;
 

Gemini’s response is as follows:

 

Here Gemini is wrong. I ran the simulator on this example and found the following:

 

However, we’ll see that Gemini’s reasoning does indeed yield the results above. Let’s continue:

 
 
 
 

Very well. Noted. Let’s continue:

 
 
 
 

So Gemini found (tax, discount, reduction) = (431, 325, 1296), whereas the simulator I used gives (431, 324, 1297). So Gemini found the correct results to within 1 euro, but it doesn’t know it. We tell it:

 

Gemini responds:

 
 

Now, we wonder if Gemini could generate a corrected PDF:

 

Gemini’s response:

 

So Gemini didn’t give me a link to a PDF, but it generated text so I could create the PDF myself. Although it’s cumbersome to include screenshots of the PDF here, I’m doing so to show readers the generative aspect of AI:

 

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

To be honest, I haven't checked whether everything in this PDF is true. In any case, it's a perfect document for a tutorial, generated in just a few seconds.

However, we can have Gemini itself verify that its PDF is correct. We start a new conversation:

 
  • in [1], we included the PDF generated by Gemini [The Problem According to Gemini.pdf];
  • in [2], [instructionsWithPDF2.txt] is identical to the instructions in [instructionsWithPDF.txt], except that we’ve added a twelfth unit test—the very one that showed the initial PDF was incorrect:
test12: (2, 2, 49500) -> (1297, 431, 324)

Curiously, it took several back-and-forth iterations before Gemini generated the correct script:

Question 2

 

Question 3

 

As has been done several times now, when the script generated and loaded into PyCharm fails, we provide Gemini with the text file containing the execution logs. Gemini understands them very well.

Question 4

 

Question 5

 

Question 6 and conclusion

We are now confident in the validity of the PDF generated by Gemini. The calculation rules provided therein are correct.

We will now do the same for the other five AIs, but we will keep our explanations very brief, except for ChatGPT, the current leader in AI. What interests us is whether or not the AI solves the three problems we present to it. In fact, the interfaces of all these AIs are very similar, and I proceeded with them in the same way as with Gemini. Readers are encouraged to replay the Gemini conversations with the AI of their choice.