Skip to content

5. Resolver os três problemas com o ChatGPT

5.1. Introdução

Aqui está uma primeira captura de ecrã de uma sessão do ChatGPT:

 
  • Em [1-3], os três problemas apresentados ao ChatGPT;
  • Em [4], o URL do ChatGPT;
  • Em [5], a versão do ChatGPT utilizada;

O ChatGPT é um produto da OpenAI disponível no URL [https://chatgpt.com/]. Para visualizar um histórico das suas sessões de perguntas e respostas, como a acima, é necessário criar uma conta. Além disso, tal como todas as outras IAs testadas, o ChatGPT limita o número de perguntas que pode fazer e o número de ficheiros que pode carregar. Quando este limite é atingido, a sessão termina e é-lhe oferecida a opção de continuar mais tarde. Os limites impostos pelo ChatGPT são atingidos muito rapidamente. Para criar este tutorial, tive de adquirir uma subscrição paga de um mês.

A interface do ChatGPT é a seguinte:

 
  • Em [1], para anexar ficheiros à pergunta feita;
  • Em [2], a pergunta feita;
  • Em [3], para executar a IA;

5.2. Problema 1

A pergunta para o ChatGPT:

  
 

O ChatGPT responde corretamente.

5.3. Problema 2

Isto envolve calcular o imposto utilizando o PDF. Para ser sincero, vamos utilizar o PDF gerado pelo Gemini, que corrige os erros do PDF original.

 
  • Em [1], fornecemos o PDF gerado pelo Gemini;
  • Em [2], adicionámos o teste unitário através do qual o Gemini demonstrou a sua superioridade:
test12 : (2, 2, 49500) -> (1297, 431, 324)

Executamos o ChatGPT. Demora cerca de 3 minutos a gerar a sua resposta. Ao contrário do Gemini, este fornece um link funcional para recuperar o script gerado. Carregamos isto no PyCharm:

 

O script [chatGPT1] funciona à primeira tentativa. Não há comparação possível; neste problema, o ChatGPT superou o Gemini.

O script [chatGPT1] fornecido pelo ChatGPT é o seguinte:

#  -*- coding: utf-8 -*-
"""
Calcul de l'impôt 2019 (revenus 2018) en Python.

Hypothèses et règles (d'après le PDF fourni) :
- Tous les revenus sont portés sur le déclarant 1 (même pour un couple).
- Abattement forfaitaire de 10 % encadré par un minimum et un maximum.
- Barème progressif 2018 appliqué au quotient familial (QF).
- Plafonnement du quotient familial : avantage par demi-part enfant limité.
- Décote et réduction de 20 % pour faibles revenus, avec la règle de repêchage.
- Ordre des correctifs : plafonnement QF -> décote -> réduction 20 %.
- Arrondis : abattement (↑), décote (↑), réduction (↑), impôt final (↓).
- On renvoie l'impôt à payer (arrondi à l'euro inf.), la décote et la réduction
  (arrondies à l'euro sup.).

Les nombres “magiques” sont regroupés en constantes, et chaque étape est isolée
dans des fonctions pour la clarté.
"""

from dataclasses import dataclass
from math import ceil, floor

#  ---------- Regulatory constants ----------

#  Professional expense allowance (floor/ceiling) for 2018 income
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

#  Scale 2018 (QF)
#  Each band is (borne_sup, rate). The first 0% bracket ends at €9,964.
BAREME_2018 = [
    (9964, 0.00),
    (27519, 0.14),
    (73779, 0.30),
    (156244, 0.41),
    (float("inf"), 0.45),
]

#  Family allowance ceiling
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  #  max benefit per 1/2 share ENFANT

#  Discount (thresholds according to number of adults / couple status)
DECOTE_SEUIL_CELIB = 1196
DECOTE_SEUIL_COUPLE = 1970

#  20% reduction: share-based thresholds
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  #  addition of 1/2 share above the base of 1 or 2 shares


#  ---------- Data structures ----------

@dataclass
class ResultatImpot:
    impot: int  #  final tax payable, rounded down to the nearest euro
    decote: int  #  discount rounded up to the nearest euro
    reduction: int  #  20% discount rounded up to the nearest euro


#  ---------- Utility functions ----------

def arrondi_sup(euros: float) -> int:
    """Arrondi à l'euro supérieur (ceil)."""
    return int(ceil(euros))


def arrondi_inf(euros: float) -> int:
    """Arrondi à l'euro inférieur (floor)."""
    return int(floor(euros))


#  ---------- Step 1: Net taxable income (RNI) ----------

def calcul_abattement(revenus_bruts: float) -> int:
    """
    Calcule l'abattement de 10 %, plafonné/planche, ARRONDI À L'EURO SUPÉRIEUR.
    """
    abattement_calcule = revenus_bruts * ABATTEMENT_TAUX
    abattement_calcule = max(ABATTEMENT_MIN, min(ABATTEMENT_MAX, abattement_calcule))
    return arrondi_sup(abattement_calcule)


def revenu_net_imposable(revenus_bruts: float) -> int:
    """RNI = revenus bruts - abattement (abattement arrondi ↑)."""
    return int(round(revenus_bruts - calcul_abattement(revenus_bruts)))


#  ---------- Step 2: Number of units ----------

def nombre_parts(adultes: int, enfants: int) -> float:
    """
    Règles : 1 part si 1 adulte, 2 parts si 2 adultes.
    Enfants : +0,5 pour les 2 premiers, +1 par enfant à partir du 3e.
    (On ignore ici tout cas particulier type parent isolé, conformément au PDF.)
    """
    parts = 1.0 if adultes == 1 else 2.0
    if enfants <= 0:
        return parts
    if enfants == 1:
        return parts + 0.5
    #  children >= 2
    parts += 1.0  #  +0.5 +0.5 for the first two
    if enfants > 2:
        parts += (enfants - 2) * 1.0  #  +1 per child from the 3rd
    return parts


def demi_parts_enfants(enfants: int) -> int:
    """Nombre de DEMI-parts dû aux enfants (utile pour le plafonnement QF)."""
    if enfants <= 0:
        return 0
    if enfants == 1:
        return 1  #  0.5 part
    #  First two children = 2 * half-parts, then +2 half-parts per child from the 3rd onwards
    return 2 + (enfants - 2) * 2


#  ---------- Step 3: Gross tax via QF ----------

def impot_brut_par_parts(qf: float) -> float:
    """Impôt par part via barème progressif 2018 (sans arrondi)."""
    imp = 0.0
    previous = 0.0
    for borne_sup, taux in BAREME_2018:
        montant_tranche = max(0.0, min(qf, borne_sup) - previous)
        imp += montant_tranche * taux
        if qf <= borne_sup:
            break
        previous = borne_sup
    return imp


def impot_brut_total(rni: float, parts: float) -> float:
    """Impôt brut total = impôt par part * nombre de parts (sans arrondi)."""
    if parts <= 0:
        return 0.0
    qf = rni / parts
    return impot_brut_par_parts(qf) * parts


#  ---------- Step 4.1: Capping the family quotient ----------

def apply_plafonnement_qf(impot_avec_enfants: float, rni: float, adultes: int, enfants: int) -> float:
    """
    Calcule l'impôt après plafonnement QF si l'avantage des enfants dépasse le plafond.
    - Impôt A : avec parts incluant enfants
    - Impôt B : avec seulement parts des adultes (1 ou 2)
    - Avantage réel = B - A
    - Avantage maximal = 1551 € par 1/2 part ENFANT
    Si avantage réel > avantage max, impôt = B - avantage max, sinon impôt = A.
    """
    parts_adultes = 1.0 if adultes == 1 else 2.0
    imp_b = impot_brut_total(rni, parts_adultes)
    imp_a = impot_avec_enfants
    avantage_reel = max(0.0, imp_b - imp_a)
    avantage_max = PF_QF_AVANTAGE_PAR_DEMI_PART * demi_parts_enfants(enfants)
    if avantage_reel > avantage_max:
        return imp_b - avantage_max
    return imp_a


#  ---------- Step 4.2: Discount and 20% reduction ----------

def seuil_decote(adultes: int) -> int:
    return DECOTE_SEUIL_COUPLE if adultes >= 2 else DECOTE_SEUIL_CELIB


def seuil_reduction_20(rni: int, adultes: int, parts: float) -> int:
    """
    Seuil d'éligibilité à la réduction de 20 % :
    - Base : 18 985 € pour 1 part, 37 970 € pour 2 parts
    - + 3 803 € par 1/2 part supplémentaire au-delà de la base correspondante.
    """
    base = REDUCTION_BASE_2_PARTS if adultes >= 2 else REDUCTION_BASE_1_PART
    base_parts = 2.0 if adultes >= 2 else 1.0
    demi_sup = max(0, int(round((parts - base_parts) * 2)))
    return base + demi_sup * REDUCTION_PAR_DEMI_PART_SUP


def calc_decote_reduction(impot_apres_plaf: float, rni: int, adultes: int, parts: float):
    """
    Calcule (decote_arrondie, reduction_arrondie, impot_apres_correctifs).
    Règle d'interaction : la décote peut s'appliquer via repêchage si, après
    application THÉORIQUE de la réduction de 20 %, l'impôt passerait sous le seuil.
    Ordre : décote -> réduction.
    """
    imp = max(0.0, impot_apres_plaf)
    decote = 0
    reduction = 0

    #  Eligible for 20% discount based on RNI
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_red  #  "below a threshold" in PDF

    #  Eligible for discount: directly below threshold, or if reduction would cause the threshold to be lowered
    s_dec = seuil_decote(adultes)
    direct_decote = imp <= s_dec
    repechage = False
    if not direct_decote and eligible_reduction:
        imp_theorique_apres_red = imp * 0.80  #  theoretical 20% reduction
        if imp_theorique_apres_red <= s_dec:
            repechage = True

    elig_decote = (direct_decote or repechage) and (imp > 0)

    #  Apply discount first (if eligible)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
        #  The discount cannot exceed the remaining tax: it is limited to
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

    #  Then apply the reduction if eligible
    if eligible_reduction and imp > 0:
        montant_reduction = imp * 0.20
        reduction = max(0, arrondi_sup(montant_reduction))
        imp = max(0.0, imp - reduction)

    return decote, reduction, imp


#  ---------- API main ----------

def calcule_impot(adultes: int, enfants: int, revenus_bruts: float) -> ResultatImpot:
    """
    Calcule l'impôt 2019 (revenus 2018) pour un foyer.
    Retourne ResultatImpot(impot, decote, reduction).
    """
    #  1) RNI
    rni = revenu_net_imposable(revenus_bruts)

    #  2) Units
    parts = nombre_parts(adultes, enfants)

    #  3) Gross tax via tax scale (with all shares)
    imp_brut_A = impot_brut_total(rni, parts)

    #  4.1) Family allowance ceiling
    imp_apres_plaf = apply_plafonnement_qf(imp_brut_A, rni, adultes, enfants)

    #  4.2) Discount then 20% reduction (with draft)
    decote, reduction, imp_corrige = calc_decote_reduction(imp_apres_plaf, rni, adultes, parts)

    #  5) Final tax rounding (↓) and bounding to 0
    imp_final = max(0, arrondi_inf(imp_corrige))

    return ResultatImpot(impot=imp_final, decote=decote, reduction=reduction)


#  ---------- Small test executable ----------

def _nearly_equal(a: int, b: int, tol: int = 1) -> bool:
    return abs(a - b) <= tol


def tests_unitaires():
    """
    Renvoie une liste de tuples (inputs, attendu, obtenu, ok) pour chaque test.
    Tolérance : ±1 € sur chaque valeur (impôt, décote, réduction).
    """
    cas = [
        #  (adults, children, income) -> (tax, discount, reduction)
        ((2, 2, 55555), (2815, 0, 0)),
        ((2, 2, 50000), (1385, 384, 346)),
        ((2, 3, 50000), (0, 720, 0)),
        ((1, 2, 100000), (19884, 0, 0)),
        ((1, 3, 100000), (16782, 0, 0)),
        ((2, 3, 100000), (9200, 0, 0)),
        ((2, 5, 100000), (4230, 0, 0)),
        ((1, 0, 100000), (22986, 0, 0)),
        ((2, 2, 30000), (0, 0, 0)),
        ((1, 0, 200000), (64211, 0, 0)),
        ((2, 3, 200000), (42843, 0, 0)),
        ((2, 2, 49500), (1297, 431, 324)),
    ]

    resultats = []
    for (adultes, enfants, revenus), attendu in cas:
        res = calcule_impot(adultes, enfants, revenus)
        obtenu = (res.impot, res.decote, res.reduction)
        ok = _nearly_equal(obtenu[0], attendu[0]) and _nearly_equal(obtenu[1], attendu[1]) and _nearly_equal(obtenu[2],
                                                                                                             attendu[2])
        resultats.append(((adultes, enfants, revenus), attendu, obtenu, ok))
    return resultats


if __name__ == "__main__":
    for inputs, attendu, obtenu, ok in tests_unitaires():
        print(f"{inputs} -> attendu={attendu}, obtenu={obtenu} : {'OK' if ok else 'ECHEC'}")

5.4. Problema 3

Agora pedimos ao ChatGPT para pesquisar as regras de cálculo de impostos na Internet:

 

Desta vez, não fornecemos o PDF que continha as regras de cálculo a seguir. Apenas fornecemos as nossas instruções no ficheiro de texto. Note-se que este ficheiro de texto contém agora 12 testes unitários, após adicionarmos, aos 11 testes iniciais, aquele utilizado pelo Gemini para demonstrar que o meu PDF inicial estava incorreto.

O ChatGPT responde em 8 minutos, fornecendo um link para descarregar o script gerado. Uma vez carregado no PyCharm, este script passa nos 12 testes. Assim, para ambos os problemas colocados, o ChatGPT acertou as respostas à primeira tentativa, superando assim o Gemini.

O ChatGPT fornece as suas fontes na sua resposta:

 

Não há mais nada a dizer — é um trabalho bem feito.

Agora, podemos pedir-lhe, tal como fizemos com o Gemini, para gerar um PDF para os alunos.

 

A resposta do ChatGPT surgiu após várias trocas de mensagens, porque o PDF gerado utilizava um tipo de letra que substituía os caracteres por quadrados. Mas, por fim, gerou o PDF. Estou a partilhá-lo porque apresenta regras diferentes das do PDF do Gemini, e fiquei a pensar qual delas estaria correta. Vamos investigar.

 
 
 

A diferença em relação ao PDF da Gemini reside na forma como o desconto é calculado. As duas IA adotam abordagens diferentes. A Gemini escreveu:

 
 
 

As duas IAs têm duas abordagens diferentes. Qual delas está certa?

5.5. Problema 4

Vamos pedir ao ChatGPT para usar o seu PDF para calcular o imposto:

Tal como em casos anteriores, ele gera um script Python que funciona à primeira tentativa. Tínhamos adicionado um teste adicional às instruções:

test13 : (1, 0, 18535) -> (359, 491, 90)

Todos os 13 testes foram concluídos com sucesso.

5.6. Voltar ao Gemini

Agora, voltamos ao Gemini, ao qual iremos apresentar o PDF do ChatGPT. Uma vez que as regras implementadas neste PDF diferem das do PDF do Gemini, perguntamo-nos o que irá acontecer:

 

O Gemini gerou primeiro um script em Python que falhou em alguns testes. Apresentámos-lhe os registos:

Pergunta 2

 

Pergunta 3

Ainda há erros. Vamos continuar.

 

Pergunta 4

Ainda ocorrem erros durante a execução:

 

Desta vez está correto.

Continuamos intrigados com o facto de, mesmo com PDFs que têm regras de cálculo bastante diferentes, ambas as IAs gerarem resultados corretos.

Fazemos a seguinte pergunta ao Gemini:

 

A pergunta completa é a seguinte:

Après tout ce qu'on a fait, compare les deux approches, la tienne et celle de ChatGPT dans les pDF que je te joins [.. selon Gemini] pour toi, [.. selon ChatGPT] pour ChatGPT. Dis-moi laquelle on doit préférer vu que sur les 13 tests générés tu les réussis tous avec l'un ou avec l'autre PDF.

A resposta do Gemini é inequívoca:

 
 
 
 
 
 

5.7. O que pensa o ChatGPT?

Fazemos ao ChatGPT a mesma pergunta que fizemos ao Gemini.

 

A resposta do ChatGPT é a seguinte:

 
 

Assim, o ChatGPT sugere um teste unitário para decidir entre os dois métodos. Duplicamos:

  • O script [gemini3] gerado pelo Gemini utilizando o seu PDF [O Problema Segundo o Gemini] como fonte é duplicado no script [gemini4];
  • O script [chatGPT3] gerado pelo ChatGPT utilizando o seu PDF [O Problema Segundo o ChatGPT] como fonte é duplicado no script [chatGPT4];

Além disso, adicionamos o teste unitário proposto pelo ChatGPT a cada um dos scripts [gemini4, chatGPT4] para distinguir entre as duas IAs.

A execução de [gemini4] produz os seguintes resultados:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm 2025.2.1.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\gemini4.py" 
Testing started at 17:45 ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours
 
SubTest failure: Traceback (most recent call last):
  File "C:\Program Files\Python313\Lib\unittest\case.py", line 58, in testPartExecutor
    yield
  File "C:\Program Files\Python313\Lib\unittest\case.py", line 556, in subTest
    yield
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py", line 234, in test_cas_verifies_simulateur_officiel
    self.assertAlmostEqual(calcul_impot, attendu_impot, delta=1, msg="Échec sur le montant de l'impôt")
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2669 != 2270 within 1 delta (399 difference) : Échec sur le montant de l'impôt
 
 
 
 
Ran 1 test in 0.010s
 
FAILED (failures=1)
 
One or more subtests failed
Failed subtests list: [Test 'test12' avec entrée (2, 0, 43333)]
 
Process finished with exit code 1

Portanto, o Gemini falha no teste adicionado pelo ChatGPT.

A execução do [chatGPT4] produz os seguintes resultados:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT4.py" 
Test (2, 2, 55555) -> obtenu (impôt=2814, décote=0, réduction=0) | attendu (2815, 0, 0) | OK
Test (2, 2, 50000) -> obtenu (impôt=1384, décote=384, réduction=347) | attendu (1385, 384, 346) | OK
Test (2, 3, 50000) -> obtenu (impôt=0, décote=721, réduction=0) | attendu (0, 720, 0) | OK
Test (1, 2, 100000) -> obtenu (impôt=19884, décote=0, réduction=0) | attendu (19884, 0, 0) | OK
Test (1, 3, 100000) -> obtenu (impôt=16782, décote=0, réduction=0) | attendu (16782, 0, 0) | OK
Test (2, 3, 100000) -> obtenu (impôt=9200, décote=0, réduction=0) | attendu (9200, 0, 0) | OK
Test (2, 5, 100000) -> obtenu (impôt=4230, décote=0, réduction=0) | attendu (4230, 0, 0) | OK
Test (1, 0, 100000) -> obtenu (impôt=22986, décote=0, réduction=0) | attendu (22986, 0, 0) | OK
Test (2, 2, 30000) -> obtenu (impôt=0, décote=0, réduction=0) | attendu (0, 0, 0) | OK
Test (1, 0, 200000) -> obtenu (impôt=64210, décote=0, réduction=0) | attendu (64211, 0, 0) | OK
Test (2, 3, 200000) -> obtenu (impôt=42842, décote=0, réduction=0) | attendu (42843, 0, 0) | OK
Test (2, 2, 49500) -> obtenu (impôt=1296, décote=431, réduction=325) | attendu (1297, 431, 324) | OK
Test (1, 0, 18535) -> obtenu (impôt=359, décote=491, réduction=90) | attendu (359, 491, 90) | OK
Test (2, 0, 43333) -> obtenu (impôt=2268, décote=0, réduction=401) | attendu (2270, 0, 400) | ECHEC
 Détails tolérance ±1€ : impôt ok? False, décote ok? True, réduction ok? True
 
Résultat global : AU MOINS UN TEST ÉCHOUE ❌
 
Process finished with exit code 0

O ChatGPT também falha no teste adicional, mas não pelas mesmas razões que o Gemini. O ChatGPT encontrou os resultados corretos, mas errou por 2 euros em vez do 1 euro exigido.

Assim, a partir de agora, utilizaremos o PDF gerado pelo ChatGPT com as seguintes IAs. Vale a pena referir que foi devido à falta de testes unitários nas minhas instruções que ambas as IAs passaram nos primeiros testes. Daí, neste exemplo específico, a importância de incluir testes unitários para casos extremos no cálculo de impostos. Uma vez que é bastante difícil conceber estes testes por conta própria, pediremos às IAs que os adicionem elas próprias.

5.8. Problema 3 com testes unitários gerados pelas IAs

Os resultados obtidos com o Gemini e o ChatGPT deixam margem para dúvidas. Será que as IAs encontraram uma solução geral que passa em todos os testes concebíveis, ou encontraram uma solução que apenas passa nos testes exigidos? Vamos recomeçar com uma solução sem PDF para forçar as IAs a acederem à Internet e a procurarem a informação de que necessitam. E vamos modificar as nossas instruções da seguinte forma:

 

O ficheiro de texto [instructionsSansPDF4.txt] já contém 14 testes obrigatórios. A estes testes, acrescentamos as seguintes instruções:


7 - tu ajouteras autant de tests unitaires que nécessaires pour vérifier les cas limites du calcul de l'impôt.
 
Pour le code tu complèteras le script suivant auquel tu auras rajouté tes propres tests.
 
# =========================
# Tests unitaires (tolérance de ±1 €)
# =========================
 
TESTS = [
    # (adultes, enfants, revenus) -> (impot, decote, reduction)
    ((2, 2, 55555), (2815, 0, 0)),
    ((2, 2, 50000), (1385, 384, 346)),
    ((2, 3, 50000), (0, 720, 0)),
    ((1, 2, 100000), (19884, 0, 0)),
    ((1, 3, 100000), (16782, 0, 0)),
    ((2, 3, 100000), (9200, 0, 0)),
    ((2, 5, 100000), (4230, 0, 0)),
    ((1, 0, 100000), (22986, 0, 0)),
    ((2, 2, 30000), (0, 0, 0)),
    ((1, 0, 200000), (64211, 0, 0)),
    ((2, 3, 200000), (42843, 0, 0)),
    ((2, 2, 49500), (1297, 431, 324)),
    ((1, 0, 18535), (359, 491, 90)),
    ((2, 0, 43333), (2270, 0, 400)),
]
 
 
def _ok(a, b, tol=1):
    return abs(a - b) <= tol
 
 
def run_tests(verbose: bool = True) -> bool:
    all_ok = True
    for (params, expected) in TESTS:
        a, e, r = params
        exp_impot, exp_decote, exp_reduc = expected
        res = calcul_impot_2019(a, e, r)
        ok_impot = _ok(res.impot, exp_impot)
        ok_decote = _ok(res.decote, exp_decote)
        ok_reduc = _ok(res.reduction, exp_reduc)
        test_ok = ok_impot and ok_decote and ok_reduc
        if verbose:
            print(
                f"Test {params} -> obtenu (impôt={res.impot}, décote={res.decote}, réduction={res.reduction}) | attendu {expected} | {'OK' if test_ok else 'ECHEC'}")
            if not test_ok:
                print(
                    f" Détails tolérance ±1€ : impôt ok? {ok_impot}, décote ok? {ok_decote}, réduction ok? {ok_reduc}")
        all_ok &= test_ok
    if verbose:
        print("\nRésultat global :", "TOUS LES TESTS PASSENT ✅" if all_ok else "AU MOINS UN TEST ÉCHOUE ❌")
    return all_ok
 
 
if __name__ == "__main__":
    run_tests()
  • Linhas 11–24: os 14 testes obrigatórios;
  • Linhas 5-55: este código provém do script gerado pelo ChatGPT. Iremos solicitar ao Gemini que utilize este código para facilitar as comparações entre os dois scripts gerados.

Começaremos com o ChatGPT:

 

A sua primeira resposta está incorreta. Indico-lhe isso fornecendo os registos de execução:

A sua segunda resposta está correta. O ChatGPT adicionou os seguintes 11 testes aos 14 testes exigidos:

# Cas limites supplémentaires (bords de paliers/arrondis)
TESTS += [
    # Abattement 10 % : plancher et plafond
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < plancher 437 => RNI faible -> impôt nul
    ((1, 0, 200000), (64211, 0, 0)),  # plafond abattement déjà couvert dans tests initiaux

    # Décote : juste en dessous / au-dessus des seuils
    ((1, 0, 25000), None),  # diagnostique
    ((2, 0, 35000), None),  # diagnostique

    # Réduction 20 % : plein droit vs écrêtement
    ((1, 0, 17000), None),  # diagnostique
    ((2, 0, 34000), None),  # diagnostique
    ((1, 0, 20000), None),  # diagnostique
    ((2, 0, 40000), None),  # diagnostique

    # Changement de parts (plafonnement QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Existem agora 25 testes unitários. Verifiquei manualmente os 11 novos testes utilizando o simulador oficial do DGIP, e eles foram aprovados.

Agora, vamos passar para o Gemini. Isto vai ser muito mais complicado. Conseguirá gerar um script que passe nos 25 testes do ChatGPT, mas só após um longo processo de depuração.

 

Segue-se o registo de depuração:

 

Estranhamente, a maioria dos testes falhou, mesmo entre os 14 testes obrigatórios, enquanto no passado o Gemini tinha gerado código que os passava todos.

A seguinte resposta do Gemini continua incorreta:

 

A resposta a seguir também não está correta:

 

Nem a resposta a seguir. Por isso, estou a mudar a minha abordagem. Estou a pedir-lhe que passe nos 25 testes que o ChatGPT passou, anexando os registos do ChatGPT:

 

O Gemini falha. Acrescentou os testes do ChatGPT. Anexo os registos da sua execução:

 

Ainda não:

 

Ainda não:

 

Ainda não:

 

Ainda não, mas está melhor:

 

O Gemini está a cometer novos erros:

 

Está a melhorar novamente:

 

Desta vez, está certo:

Sem dúvida, neste exemplo específico de cálculo do imposto de 2019 com as restrições especificadas no ficheiro de instruções, o ChatGPT foi mais preciso do que o Gemini. Mas este é apenas um exemplo.

Podemos ir mais longe. Podemos pedir ao Gemini para regenerar um PDF com base nas regras de cálculo que utilizou para passar nos 25 testes. Queremos ver se alterou o seu raciocínio inicial relativamente aos cálculos do desconto e da redução de 20%:

Desta vez, o Gemini gerou um ficheiro Markdown que eu converti para PDF [O Problema Segundo o Gemini Versão 2]. E o Gemini alterou, de facto, o seu raciocínio:

 
 

Podemos ver que o cálculo específico do desconto e a regra de transferência já não estão presentes. A Gemini adotou agora o raciocínio do ChatGPT.