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:

    27519    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    20163.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 - Exprime-toi en français.

2 - Peux-tu générer un script Python permettant de calculer l'impôt payé par les familles en 2019 sur leurs revenus de 2018.

3 - Tu t'aideras du document PDF que j'ai joint et qui explique les calculs à faire.

4 - Tu dois faire attention aux points suivants :

- plafonnement du quotient familial. Il y a des seuils à vérifier.
- calcul de la décote dans certains cas. Il y a des seuils à vérifier.
- calcul de la réduction de 20% dans certains cas. Il y a des seuils à vérifier.
- plafonnement de l'abattement de 10% sur les revenus annuels dans certains cas.
- tu considèreras que tous les revenus sont à déclarer pour le déclarant 1 même si le couple est marié.

5 - Tu ajouteras au script généré des tests unitaires pour les cas suivants.

Dans ces tests on appelle :

adultes :nombre d'adultes du foyer fiscal
enfants : nombre d'enfants du foyer fiscal
revenus : revenus nets annuels avant impôt, ç-à-d avant le 1er calcul de l'abattement.
impot : l'impôt à payer
decote :la décote éventuelle du foyer fiscal
reduction : la réduction de 20% pour les faibles revenus

Voici les 11 tests à vérifier. Ils ont tous été vérifiés manuellement sur le simulateur officiel
du calcul de l'impôt 2019 [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.html].
Si tu utilises ce simulateur les revenus doivent être associés au seul déclarant 1 dans le cas d'un couple, le déclarant 2 étant alors ignoré. Lorsqu'on répartit les revenus
sur deux déclarants, on n'obtient pas le même résultat.

On utilise la syntaxe (adultes, enfants, revenus) -> (impot, decote, reduction) pour dire que le script reçoit les entrées
(adultes, enfants, revenus) et produit les résultats (impot, decote, 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 - Il peut y avoir des problèmes d'arrondis. Tu procèderas comme suit
- l'impôt à payer sera arrondi à l'euro inférieur,
- la décote sera arrondie à l'euro supérieur,
- la réduction de 20 % sera arrondie à l'euro supérieur.
- l'abattement de 10% sera arrondi à l'euro supérieur

Fais tous les tests unitaires à l'euro près à cause de ces éventuelles erreurs d'arrondi.
Ne cherche pas à avoir les valeurs exactes ci-dessus mais ces valeurs à 1 euro près.

7 - Evite de chercher sur internet. Le PDF que je te donne est correct.
Ne donne ton résultat que lorsque tu as passé les 11 tests unitaires avec succès.

8 - si l'un des tests échoue et que tu es bloqué, affiche ton raisonnement pour ce test
afin que je puisse t'aider.

9- Mets des commentaires détaillés dans le script que tu génères.
Mets le barème progressif dans une liste ou dictionnaire puis utilise cette liste ou dictionnaire
Mets les nombres en dur (nombres magiques) que tu utilises dans des constantes
Utilise des fonctions pour séparer les étapes du calcul.
Ecris le tout en français

9 - n'affiche pas le code généré à l'écran. Donne-moi simplement un lien pour le récupérer.
Si les tests unitaires échouent, je te donnerai les logs de l'exécution du script pour que
tu voies tes erreurs.

10 - si c'est possible, indique le temps en minutes et secondes que tu as mis pour produire
le script demandé.

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
    suites = [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 pour le calcul de l'impôt 2019 sur les revenus de 2018 en France,
basé sur les règles simplifiées fournies dans le document PDF.
"""

import math
import unittest

#  --- CONSTANTES DE CALCUL POUR L'IMPÔT 2019 ---
#  All values are taken from the PDF document supplied.

#  Ceilings for the 10% allowance on annual income
PLAFOND_ABATTEMENT_10_POURCENT = 12502.0
MINIMUM_ABATTEMENT_10_POURCENT = 437.0

#  2019 income tax scale
#  Structure: (Maximum bracket threshold, Tax rate, Amount to be deducted per share)
BAREME_IMPOT_2019 = [
    {"seuil": 9964, "taux": 0.0, "deduction_par_part": 0},
    {"seuil": 27519, "taux": 0.14, "deduction_par_part": 1394.96},
    {"seuil": 73779, "taux": 0.30, "deduction_par_part": 5798},
    {"seuil": 156244, "taux": 0.41, "deduction_par_part": 13913.69},
    {"seuil": float('inf'), "taux": 0.45, "deduction_par_part": 20163.45}
]

#  Family allowance ceiling
GAIN_MAX_DEMI_PART = 1551.0  #  Maximum gain per additional half-share

#  Thresholds and amounts for the discount
SEUIL_APPLICATION_DECOTE = {"celibataire": 1595.0, "couple": 2627.0}
SEUIL_CALCUL_DECOTE = {"celibataire": 1196.0, "couple": 1970.0}

#  Thresholds for the 20% tax reduction
SEUIL_BASE_REDUCTION_20 = {"celibataire": 21037.0, "couple": 42074.0}
MAJORATION_SEUIL_REDUCTION_PAR_DEMI_PART = 3797.0


def calculer_nombre_parts(adultes: int, enfants: int) -> float:
    """
    Calcule le nombre de parts fiscales du foyer.
    - Chaque parent amène 1 part.
    - Les deux premiers enfants amènent chacun 1/2 part.
    - Les enfants suivants amènent une part chacun.
    """
    if adultes not in [1, 2]:
        raise ValueError("Le nombre d'adultes doit être 1 ou 2.")

    parts = float(adultes)
    if enfants > 0:
        parts += 0.5 * min(enfants, 2)
    if enfants > 2:
        parts += 1.0 * (enfants - 2)

    return parts


def calculer_revenu_imposable(revenus: float) -> float:
    """
    Calcule le revenu net imposable après l'abattement de 10%.
    Cet abattement est plafonné.
    L'abattement est arrondi à l'euro supérieur.
    """
    abattement_calcule = revenus * 0.1
    #  The allowance is capped between a minimum and a maximum
    abattement = min(PLAFOND_ABATTEMENT_10_POURCENT, max(MINIMUM_ABATTEMENT_10_POURCENT, abattement_calcule))

    #  The statement requires the abatement to be rounded up to the nearest euro
    abattement_arrondi = math.ceil(abattement)

    return revenus - abattement_arrondi


def calculer_impot_brut_base(revenu_imposable: float, nombre_parts: float) -> float:
    """
    Calcule l'impôt brut selon le barème progressif.
    """
    if nombre_parts <= 0:
        return 0.0

    quotient_familial = revenu_imposable / nombre_parts

    tranche_applicable = None
    for tranche in BAREME_IMPOT_2019:
        if quotient_familial <= tranche["seuil"]:
            tranche_applicable = tranche
            break

    #  If the QF is greater than all the thresholds, the last slice is used
    if tranche_applicable is None:
        tranche_applicable = BAREME_IMPOT_2019[-1]

    #  Gross tax calculation formula
    impot_brut = (revenu_imposable * tranche_applicable["taux"]) - (
            tranche_applicable["deduction_par_part"] * nombre_parts)

    return max(0.0, impot_brut)


def appliquer_plafonnement_qf(impot_brut_avec_enfants: float, revenu_imposable: float, adultes: int,
                              enfants: int) -> float:
    """
    Applique le plafonnement du quotient familial si nécessaire.
    L'impôt final ne peut être inférieur à l'impôt calculé sans les enfants,
    diminué d'un gain maximal par demi-part.
    """
    if enfants == 0:
        return impot_brut_avec_enfants

    #  Tax calculation without children's shares
    parts_sans_enfants = calculer_nombre_parts(adultes, 0)
    impot_sans_enfants = calculer_impot_brut_base(revenu_imposable, parts_sans_enfants)

    #  Calculation of maximum child-related earnings
    parts_avec_enfants = calculer_nombre_parts(adultes, enfants)
    demi_parts_enfants = (parts_avec_enfants - parts_sans_enfants) * 2
    gain_maximal = demi_parts_enfants * GAIN_MAX_DEMI_PART

    impot_minimal_plafone = impot_sans_enfants - gain_maximal

    #  The tax used is the higher of the two calculations
    return max(impot_brut_avec_enfants, impot_minimal_plafone)


def appliquer_decote(impot_avant_decote: float, adultes: int) -> tuple[float, float]:
    """
    Applique la décote si l'impôt est inférieur à certains seuils.
    La décote est arrondie à l'euro supérieur.
    """
    statut = "couple" if adultes == 2 else "celibataire"
    seuil_application = SEUIL_APPLICATION_DECOTE[statut]
    seuil_calcul = SEUIL_CALCUL_DECOTE[statut]

    decote = 0.0
    #  The discount applies if the tax is below the application threshold
    if impot_avant_decote < seuil_application:
        #  Discount formula: threshold - 0.75 * gross tax
        decote_calculee = seuil_calcul - (0.75 * impot_avant_decote)

        #  Rounded up to the nearest euro
        decote = math.ceil(decote_calculee)

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

    impot_apres_decote = impot_avant_decote - decote
    return impot_apres_decote, decote


def appliquer_reduction_20_pourcent(impot_avant_reduction: float, revenu_imposable: float, adultes: int,
                                    enfants: int) -> tuple[float, float]:
    """
    Applique la réduction de 20% sous condition de revenus.
    La réduction est arrondie à l'euro supérieur.
    """
    statut = "couple" if adultes == 2 else "celibataire"
    seuil_base = SEUIL_BASE_REDUCTION_20[statut]

    #  The threshold is increased for each half-percentage point for children
    parts_avec_enfants = calculer_nombre_parts(adultes, enfants)
    parts_adultes = calculer_nombre_parts(adultes, 0)
    demi_parts_enfants = (parts_avec_enfants - parts_adultes) * 2
    seuil_final = seuil_base + (demi_parts_enfants * MAJORATION_SEUIL_REDUCTION_PAR_DEMI_PART)

    reduction = 0.0
    #  The reduction applies if taxable income is below the threshold
    if revenu_imposable < seuil_final:
        reduction_calculee = impot_avant_reduction * 0.20

        #  Rounded up to the nearest euro
        reduction = math.ceil(reduction_calculee)

    impot_apres_reduction = impot_avant_reduction - reduction
    return max(0.0, impot_apres_reduction), reduction


def calculer_impot(adultes: int, revenus: float, enfants: int) -> dict:
    """
    Orchestre le calcul complet de l'impôt net à payer.
    """
    #  Step 1: Calculating taxable income
    revenu_imposable = calculer_revenu_imposable(revenus)

    #  Step 2: Calculating the number of shares
    nombre_parts = calculer_nombre_parts(adultes, enfants)

    #  Step 3: Gross tax calculation
    impot_brut_initial = calculer_impot_brut_base(revenu_imposable, nombre_parts)

    #  Step 4: Applying the QF cap
    impot_apres_plafonnement = appliquer_plafonnement_qf(impot_brut_initial, revenu_imposable, adultes, enfants)

    #  Step 5: Applying the discount
    impot_apres_decote, decote_calculee = appliquer_decote(impot_apres_plafonnement, adultes)

    #  Step 6: Apply the 20% discount
    impot_avant_arrondi_final, reduction_calculee = appliquer_reduction_20_pourcent(impot_apres_decote,
                                                                                    revenu_imposable, adultes, enfants)

    #  Step 7: Final tax rounding down to the nearest euro
    impot_final = math.floor(impot_avant_arrondi_final)

    return {
        "impot": impot_final,
        "decote": decote_calculee,
        "reduction": reduction_calculee
    }


#  --- TESTS UNITAIRES ---
#  Validation of the script with the cases provided
class TestCalculImpot2019(unittest.TestCase):

    def _verifier_resultat(self, adultes, enfants, revenus, impot_attendu, decote_attendue, reduction_attendue):
        """Fonction utilitaire pour exécuter un test et vérifier les résultats à 1 euro près."""
        resultat = calculer_impot(adultes=adultes, revenus=revenus, enfants=enfants)

        self.assertAlmostEqual(resultat["impot"], impot_attendu, delta=1,
                               msg=f"Test ({adultes}, {enfants}, {revenus}): Impôt attendu {impot_attendu}, obtenu {resultat['impot']}")

        self.assertAlmostEqual(resultat["decote"], decote_attendue, delta=1,
                               msg=f"Test ({adultes}, {enfants}, {revenus}): Décote attendue {decote_attendue}, obtenue {resultat['decote']}")

        self.assertAlmostEqual(resultat["reduction"], reduction_attendue, delta=1,
                               msg=f"Test ({adultes}, {enfants}, {revenus}): Réduction attendue {reduction_attendue}, obtenue {resultat['reduction']}")

    def test_cas_1(self):
        # (2, 2, 55555) -> (2815, 0, 0)
        self._verifier_resultat(2, 2, 55555, 2815, 0, 0)

    def test_cas_2(self):
        # (2, 2, 50000) -> (1385, 384, 346)
        self._verifier_resultat(2, 2, 50000, 1385, 384, 346)

    def test_cas_3(self):
        # (2, 3, 50000) -> (0, 720, 0)
        self._verifier_resultat(2, 3, 50000, 0, 720, 0)

    def test_cas_4(self):
        # (1, 2, 100000) -> (19884, 0, 0)
        self._verifier_resultat(1, 2, 100000, 19884, 0, 0)

    def test_cas_5(self):
        # (1, 3, 100000) -> (16782, 0, 0)
        self._verifier_resultat(1, 3, 100000, 16782, 0, 0)

    def test_cas_6(self):
        # (2, 3, 100000) -> (9200, 0, 0)
        self._verifier_resultat(2, 3, 100000, 9200, 0, 0)

    def test_cas_7(self):
        # (2, 5, 100000) -> (4230, 0, 0)
        self._verifier_resultat(2, 5, 100000, 4230, 0, 0)

    def test_cas_8(self):
        # (1, 0, 100000) -> (22986, 0, 0)
        self._verifier_resultat(1, 0, 100000, 22986, 0, 0)

    def test_cas_9(self):
        # (2, 2, 30000) -> (0, 0, 0)
        self._verifier_resultat(2, 2, 30000, 0, 0, 0)

    def test_cas_10(self):
        # (1, 0, 200000) -> (64211, 0, 0)
        self._verifier_resultat(1, 0, 200000, 64211, 0, 0)

    def test_cas_11(self):
        # (2, 3, 200000) -> (42843, 0, 0)
        self._verifier_resultat(2, 3, 200000, 42843, 0, 0)


if __name__ == '__main__':
    print("Exécution des tests unitaires...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

    #  Example of how to use the calculator for a specific case
    print("\n--- Exemple de calcul ---")
    revenus_annuels = 50000
    nombre_adultes = 2
    nombre_enfants = 2

    resultat_calcul = calculer_impot(adultes=nombre_adultes, revenus=revenus_annuels, enfants=nombre_enfants)

    print(f"Pour un couple ({nombre_adultes} adultes) avec {nombre_enfants} enfants et {revenus_annuels}€ de revenus :")
    print(f"  - Impôt à payer : {resultat_calcul['impot']}€")
    print(f"  - Montant de la décote : {resultat_calcul['decote']}€")
    print(f"  - Montant de la réduction : {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 - Exprime-toi en français.
 
2 - Peux-tu générer un script Python permettant de calculer l'impôt payé par les familles en 2019 sur leurs revenus de 2018.
 
3 - Tu t'aideras des sources que tu trouveras sur internet. Dans ta réponse indique-moi ces sources.
 
4 - Tu dois faire attention aux points suivants :

  • 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 17:23 ...
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(impot, 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
    impot, _, _ = calculer_impot_final(2, 3, 50000)
                  ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculer_impot_final
    decote, impot_apres_decote = calculer_decote(impot_apres_plafonnement, adultes)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculer_decote
    decote = seuil_decote - (impot_avant_decote * COFFICIENT_DECOTE)
                                                  ^^^^^^^^^^^^^^^^^
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
    impot_calcule, decote_calculee, reduction_calculee = calculer_impot_final(adultes, enfants, revenus)
                                                         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculer_impot_final
    decote, impot_apres_decote = calculer_decote(impot_apres_plafonnement, adultes)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculer_decote
    decote = seuil_decote - (impot_avant_decote * COFFICIENT_DECOTE)
                                                  ^^^^^^^^^^^^^^^^^
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

# ==============================================================================
#  CONSTANTES POUR L'IMPOSITION DES REVENUS 2018 (PAYÉE EN 2019)
# ==============================================================================
BAREME_IMPOT_2019 = [
    {'seuil': 0, 'taux': 0.0},
    {'seuil': 9964, 'taux': 0.14},
    {'seuil': 27519, 'taux': 0.30},
    {'seuil': 73779, 'taux': 0.41},
    {'seuil': 156244, 'taux': 0.45},
]
ABATTEMENT_FORFAITAIRE_TAUX = 0.10
ABATTEMENT_FORFAITAIRE_MINIMUM = 437
ABATTEMENT_FORFAITAIRE_PLAFOND = 12502
PLAFONNEMENT_AVANTAGE_QF_DEMI_PART = 1551
SEUIL_DECOTE_CELIBATAIRE = 1196
SEUIL_DECOTE_COUPLE = 1970
COEFFICIENT_DECOTE = 0.75
TAUX_REDUCTION_20_POURCENT = 0.20
SEUIL_RFR_REDUCTION_CELIBATAIRE = 18985
SEUIL_RFR_REDUCTION_COUPLE = 37970
MAJORATION_RFR_REDUCTION_PAR_DEMI_PART = 3803


# ==============================================================================
#  FONCTIONS DE CALCUL
# ==============================================================================

def calculer_nombre_parts(adultes: int, enfants: int) -> float:
    """Calcule le nombre de parts du foyer fiscal."""
    if adultes < 1: return 0
    parts = float(adultes)
    if enfants >= 1: parts += 0.5
    if enfants >= 2: parts += 0.5
    if enfants >= 3: parts += (enfants - 2) * 1.0
    return parts


def calculer_revenu_imposable(revenus_bruts: float) -> float:
    """Calcule le revenu net imposable après abattement de 10%."""
    if revenus_bruts == 0: return 0.0
    abattement = revenus_bruts * ABATTEMENT_FORFAITAIRE_TAUX
    if abattement < ABATTEMENT_FORFAITAIRE_MINIMUM: abattement = ABATTEMENT_FORFAITAIRE_MINIMUM
    if abattement > ABATTEMENT_FORFAITAIRE_PLAFOND: abattement = ABATTEMENT_FORFAITAIRE_PLAFOND
    abattement = math.ceil(abattement)
    revenu_imposable = revenus_bruts - abattement
    return revenu_imposable if revenu_imposable > 0 else 0


def calculer_impot_brut_par_tranche(revenu_imposable: float, nombre_parts: float) -> float:
    """Calcule l'impôt brut en appliquant le barème progressif."""
    if nombre_parts <= 0 or revenu_imposable <= 0: return 0.0
    quotient_familial = revenu_imposable / nombre_parts
    impot_par_part = 0.0
    for i in range(len(BAREME_IMPOT_2019)):
        tranche = BAREME_IMPOT_2019[i]
        seuil_actuel = tranche['seuil']
        taux = tranche['taux']
        if quotient_familial > seuil_actuel:
            base_imposable_tranche = 0
            if i < len(BAREME_IMPOT_2019) - 1:
                seuil_suivant = BAREME_IMPOT_2019[i + 1]['seuil']
                base_imposable_tranche = min(quotient_familial, seuil_suivant) - seuil_actuel
            else:
                base_imposable_tranche = quotient_familial - seuil_actuel
            impot_par_part += base_imposable_tranche * taux
    return impot_par_part * nombre_parts


def appliquer_plafonnement_qf(revenu_imposable: float, nombre_parts: float, adultes: int,
                              impot_brut_famille: float) -> float:
    """Applique le plafonnement des effets du quotient familial."""
    if nombre_parts <= adultes: return impot_brut_famille
    impot_base = calculer_impot_brut_par_tranche(revenu_imposable, float(adultes))
    demi_parts_enfants = (nombre_parts - adultes) * 2
    avantage_qf_max = PLAFONNEMENT_AVANTAGE_QF_DEMI_PART * demi_parts_enfants
    avantage_qf_reel = impot_base - impot_brut_famille
    if avantage_qf_reel > avantage_qf_max:
        return impot_base - avantage_qf_max
    return impot_brut_famille


def calculer_impot_final(adultes: int, enfants: int, revenus: float) -> (int, int, int):
    """Orchestre le calcul complet de l'impôt."""
    revenu_imposable = calculer_revenu_imposable(revenus)
    nombre_parts = calculer_nombre_parts(adultes, enfants)
    impot_brut = calculer_impot_brut_par_tranche(revenu_imposable, nombre_parts)
    impot_apres_plafonnement = appliquer_plafonnement_qf(revenu_imposable, nombre_parts, adultes, impot_brut)

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

    #  --- LOGIQUE CORRIGÉE POUR INTERACTION DÉCOTE / RÉDUCTION ---
    decote_calculee = 0.0
    reduction_calculee = 0.0
    impot_a_traiter = impot_apres_plafonnement

    #  1. Determining eligibility for the 20% discount
    est_eligible_reduction = False
    if adultes >= 2:
        seuil_rfr = SEUIL_RFR_REDUCTION_COUPLE + max(0, (nombre_parts - 2) * 2) * MAJORATION_RFR_REDUCTION_PAR_DEMI_PART
    else:
        seuil_rfr = SEUIL_RFR_REDUCTION_CELIBATAIRE + max(0, (
                nombre_parts - 1) * 2) * MAJORATION_RFR_REDUCTION_PAR_DEMI_PART
    if revenu_imposable < seuil_rfr:
        est_eligible_reduction = True

    #  2. Determining eligibility for the discount
    seuil_decote_foyer = SEUIL_DECOTE_COUPLE if adultes >= 2 else SEUIL_DECOTE_CELIBATAIRE
    reduction_theorique = math.ceil(impot_a_traiter * TAUX_REDUCTION_20_POURCENT) if est_eligible_reduction else 0

    if (impot_a_traiter - reduction_theorique) < seuil_decote_foyer:
        decote_calculee = seuil_decote_foyer - (impot_a_traiter * COEFFICIENT_DECOTE)
        decote_calculee = math.ceil(decote_calculee)
        #  CORRECTION: The discount returned cannot exceed the tax it cancels out
        decote_calculee = min(decote_calculee, math.ceil(impot_a_traiter))
        impot_a_traiter -= decote_calculee
        impot_a_traiter = max(0.0, impot_a_traiter)

    #  3. Apply the 20% discount if eligible
    if est_eligible_reduction:
        reduction_calculee = math.ceil(impot_a_traiter * TAUX_REDUCTION_20_POURCENT)
        impot_a_traiter -= reduction_calculee
        impot_a_traiter = max(0.0, impot_a_traiter)

    impot_final = math.floor(impot_a_traiter)

    return impot_final, int(decote_calculee), int(reduction_calculee)


# ==============================================================================
#  TESTS UNITAIRES
# ==============================================================================

class TestCalculImpot2019(unittest.TestCase):
    def _verifier_cas(self, adultes, enfants, revenus, attendu):
        impot_attendu, decote_attendue, reduction_attendue = attendu
        impot_calcule, decote_calculee, reduction_calculee = calculer_impot_final(adultes, enfants, revenus)
        self.assertAlmostEqual(impot_calcule, impot_attendu, delta=1, msg=f"Impôt ({impot_calcule} vs {impot_attendu})")
        self.assertAlmostEqual(decote_calculee, decote_attendue, delta=1,
                               msg=f"Décote ({decote_calculee} vs {decote_attendue})")
        self.assertAlmostEqual(reduction_calculee, reduction_attendue, delta=1,
                               msg=f"Réduction ({reduction_calculee} vs {reduction_attendue})")

    def test_cas_1(self): self._verifier_cas(2, 2, 55555, (2815, 0, 0))

    def test_cas_2(self): self._verifier_cas(2, 2, 50000, (1385, 384, 346))

    def test_cas_3(self): self._verifier_cas(2, 3, 50000, (0, 720, 0))

    def test_cas_4(self): self._verifier_cas(1, 2, 100000, (19884, 0, 0))

    def test_cas_5(self): self._verifier_cas(1, 3, 100000, (16782, 0, 0))

    def test_cas_6(self): self._verifier_cas(2, 3, 100000, (9200, 0, 0))

    def test_cas_7(self): self._verifier_cas(2, 5, 100000, (4230, 0, 0))

    def test_cas_8(self): self._verifier_cas(1, 0, 100000, (22986, 0, 0))

    def test_cas_9(self): self._verifier_cas(2, 2, 30000, (0, 0, 0))

    def test_cas_10(self): self._verifier_cas(1, 0, 200000, (64211, 0, 0))

    def test_cas_11(self): self._verifier_cas(2, 3, 200000, (42843, 0, 0))


if __name__ == '__main__':
    print("Lancement des tests unitaires pour le calcul de l'impôt 2019...")
    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.