Skip to content

5. Resolução dos três problemas com o ChatGPT

5.1. Introdução

Eis uma primeira captura de ecrã de uma sessão do ChatGPT:

 
  • No [1-3], os três problemas apresentados no ChatGPT;
  • Na sessão [4], o URL da sessão ChatGPT;
  • No [5], a versão do ChatGPT utilizada;

O ChatGPT é um produto do OpenAI disponível no URL e no [https://chatgpt.com/]. Para ter um histórico das suas sessões de perguntas e respostas, tal como acima, terá de criar uma conta. Além disso, tal como todas as outras versões testadas, o IA limita o número de perguntas e o número de ficheiros descarregados. Quando este limite é atingido, a sessão é encerrada e é-lhe sugerido que a continue mais tarde. Os limites impostos pelo ChatGPT são atingidos muito rapidamente. Para realizar este tutorial, tive de subscrever um plano pago de um mês.

A interface do ChatGPT é a seguinte:

 
  • No [1], para anexar ficheiros à pergunta colocada;
  • No [2], a pergunta colocada;
  • No [3], para iniciar a execução do IA;

5.2. O problema 1

A pergunta no ChatGPT:

  
 

O ChatGPT responde corretamente.

5.3. Problema 2

Trata-se do cálculo do imposto com o PDF. Para ser sincero, vamos utilizar o PDF gerado pelo Gemini, que corrige os erros do PDF inicial.

 
  • No [1], indicámos o PDF gerado pelo Gemini;
  • No [2], foi adicionado 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 efetivamente um link funcional para recuperar o script gerado. Carregamos este script no PyCharm:

 

O script [chatGPT1] funciona à primeira tentativa. Não há comparação: neste problema, o ChatGPT teve um desempenho superior ao do 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

# ---------- Constantes regulamentares ----------

# Dedução de despesas profissionais (limite mínimo/máximo) para rendimentos de 2018
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

# Tabela de 2018 (QF)
# Cada faixa corresponde a (borne_sup, taxa). A primeira faixa de 0% termina nos 9 964 €.
BAREME_2018 = [
    (9964, 0.00),
    (27519, 0.14),
    (73779, 0.30),
    (156244, 0.41),
    (float("inf"), 0.45),
]

# Limite máximo do quociente familiar
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # benefício máximo por 1/2 parte ENFANT

# Redução (limiares em função do número de adultos / situação do casal)
DECOTE_SEUIL_CELIB = 1196
DECOTE_SEUIL_COUPLE = 1970

# Redução de 20 %: limiares em função das quotas
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  # acréscimo por 1/2 parte para além da base de 1 ou 2 partes


# ---------- Estruturas de dados ----------

@dataclass
class ResultatImpot:
    impot: int  # imposto final a pagar, arredondado para o euro inferior
    decote: int  # desconto arredondado para o euro superior
    reduction: int  # redução de 20 %, arredondada para o euro superior


# ---------- Funções utilitárias ----------

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))


# ---------- Etapa 1: Rendimento Líquido Tributável (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)))


# ---------- Etapa 2: Número de quotas ----------

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
     # filhos >= 2
    parts += 1.0  # +0,5 +0,5 para os dois primeiros
    if enfants > 2:
        parts += (enfants - 2) * 1.0  # +1 por criança a partir da 3.ª
    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 quota
     # Dois primeiros filhos = 2 * meia quota, depois +2 meias quotas por filho a partir do terceiro
    return 2 + (enfants - 2) * 2


# ---------- Etapa 3: Imposto bruto através de 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


# ---------- Etapa 4.1: Limite máximo do quociente familiar ----------

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


# ---------- Etapa 4.2: Desconto e redução de 20 % ----------

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

     # Elegibilidade para a redução de 20 % com base em RNI
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_red  # «inferior a um limiar» no PDF

     # Elegibilidade para desconto: diretamente abaixo do limiar, ou repescagem se a redução fizer com que o valor fique abaixo do limiar
    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  # redução teórica de 20%
        if imp_theorique_apres_red <= s_dec:
            repechage = True

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

     # Aplicar primeiro a redução (se elegível)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
         # O abatimento não pode exceder o imposto restante: é limitado
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

     # Em seguida, aplicar a redução, se elegível
    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 principal ----------

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) Quotas
    parts = nombre_parts(adultes, enfants)

     # 3) Imposto bruto segundo a tabela (com todas as quotas)
    imp_brut_A = impot_brut_total(rni, parts)

     # 4.1) Limite máximo do quociente familiar
    imp_apres_plaf = apply_plafonnement_qf(imp_brut_A, rni, adultes, enfants)

     # 4.2) Desconto e, em seguida, redução de 20 % (com repescagem)
    decote, reduction, imp_corrige = calc_decote_reduction(imp_apres_plaf, rni, adultes, parts)

     # 5) Arredondamento final do imposto (↓) e limitação a 0
    imp_final = max(0, arrondi_inf(imp_corrige))

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


# ---------- Pequeno programa de teste ----------

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 = [
         # (adultos, crianças, rendimentos) -> (imposto, desconto, redução)
        ((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. O problema 3

Agora, pedimos ao ChatGPT para procurar na Internet as regras de cálculo do imposto:

 

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

O ChatGPT responde em 8 minutos e fornece um link para descarregar o script gerado. Uma vez carregado no PyCharm, este script passa nos 12 testes. Assim, relativamente aos dois problemas colocados, o ChatGPT respondeu corretamente à primeira tentativa, superando assim o Gemini.

O ChatGPT indica as suas fontes na resposta:

 

Não há nada a dizer, é um excelente trabalho.

Agora, podemos pedir-lhe, tal como fizemos com o Gemini, que gere um PDF para os estudantes.

 

A resposta de ChatGPT foi obtida após várias idas e vindas, pois o PDF gerado utilizava um tipo de letra que substituía os caracteres por um quadrado. Mas, por fim, gerou o PDF. Apresento-o porque fornece regras diferentes do PDF da Gemini e perguntei-me, então, quem estaria certo. Vamos investigar.

 
 
 

A diferença em relação ao PDF da Gemini reside no cálculo do desconto. Os dois IA não seguem a mesma abordagem. A Gemini tinha escrito:

 
 
 

Os dois IA têm duas abordagens diferentes. Quem está certo?

5.5. Problema 4

Vamos pedir ao ChatGPT que se baseie no seu PDF para calcular o imposto:

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

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

Os 13 testes foram concluídos com sucesso.

5.6. De volta ao Gemini

Agora, voltamos ao Gemini, ao qual vamos apresentar o PDF a partir do ChatGPT. Uma vez que as regras implementadas neste PDF são diferentes das implementadas no PDF do Gemini, podemos questionar-nos sobre o que irá acontecer:

 

O Gemini gerou, em primeiro lugar, um script Python que falhava nos testes. Apresentámos-lhe os registos:

Pergunta 2

 

Pergunta 3

Ainda há erros. Continuamos.

 

Pergunta 4

Continuam a ocorrer erros na execução:

 

Desta vez está tudo bem.

No entanto, é intrigante que, apesar de os PDF terem regras de cálculo bastante diferentes, os IA gerem ambos resultados corretos.

Colocamos a seguinte questão 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 é categórica:

 
 
 
 
 
 

5.7. O que pensa o ChatGPT

Fazemos a ChatGPT a mesma pergunta que fizemos à Gemini.

 

A resposta de ChatGPT é a seguinte:

 
 

Assim, o ChatGPT propõe-nos um teste unitário para decidir entre os dois métodos. Reproduzimos:

  • O script [gemini3] gerado pelo Gemini, tomando como fonte o seu PDF; o [Le problème selon Gemini] é duplicado no script [gemini4];
  • O script [chatGPT3], gerado pelo ChatGPT a partir dos seus scripts PDF e [Le problème selon ChatGPT], é duplicado no script [chatGPT4];

Além disso, adiciona-se em cada um dos scripts [gemini4, chatGPT4] o teste unitário proposto pelo ChatGPT para distinguir os dois IA.

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 por ChatGPT.

A execução de [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 adicionado, mas não pelas mesmas razões que o Gemini. O ChatGPT encontrou os resultados corretos, mas com uma diferença de 2 euros, em vez do 1 euro exigido.

Portanto, a partir de agora, será o PDF gerado pelo ChatGPT que utilizaremos com os seguintes IA. É importante referir que foi devido à falta de testes unitários propostos nas minhas instruções que os dois IA passaram ambos nos primeiros testes. Daí, neste exemplo específico, a importância de incluir testes unitários para os casos-limite do cálculo do imposto. Uma vez que é bastante difícil imaginar esses testes por conta própria. Vamos pedir aos IA que os adicionem eles próprios.

5.8. O problema 3 com testes unitários gerados pelos IA

Os resultados obtidos com o Gemini e o ChatGPT deixam dúvidas. Será que os IA encontraram uma solução geral que passa em todos os testes imagináveis ou encontraram apenas uma solução que passa nos testes impostos? Vamos recomeçar com uma solução sem o PDF, para obrigar o IA a aceder à Internet e procurar as informações de que necessita. E alteramos 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.

# =========================
# Testes unitários (tolerância de ±1 €)
# =========================

TESTS = [
     # (adultos, crianças, rendimentos) -> (imposto, desconto, redução)
    ((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 predefinidos;
  • Linhas 5-55: este código provém do script gerado por ChatGPT. Vamos obrigar o Gemini a utilizar este código para facilitar as comparações entre os dois scripts gerados.

Começamos pelo ChatGPT:

 

A sua primeira resposta está incorreta. Digo-lhe isso, fornecendo-lhe os registos da execução:

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

# Casos-limite adicionais (limites de escalões/arredondamentos)
TESTS += [
     # Abatimento de 10 %: limite mínimo e máximo
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < limite mínimo de 437 => RNI baixo -> imposto nulo
    ((1, 0, 200000), (64211, 0, 0)),  # limite máximo da dedução já abrangido nos testes iniciais

     # Desconto: logo abaixo / acima dos limiares
    ((1, 0, 25000), None),  # diagnóstico
    ((2, 0, 35000), None),  # diagnóstico

     # Redução de 20 %: direito integral vs. limitação
    ((1, 0, 17000), None),  # diagnóstico
    ((2, 0, 34000), None),  # diagnóstico
    ((1, 0, 20000), None),  # diagnóstico
    ((2, 0, 40000), None),  # diagnóstico

     # Alteração de quotas (limite máximo 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 com o simulador oficial do DGIP e está tudo bem.

Agora, passamos para o Gemini. Isto vai ser muito mais complicado. Ele vai conseguir gerar um script que passe nos 25 testes do ChatGPT, mas só depois de uma longa depuração.

 

Segue-se a lista de depuração:

 

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

A resposta seguinte do Gemini continua a não estar correta:

 

A resposta seguinte também não:

 

A resposta seguinte também não. Por isso, mudo de estratégia. Peço-lhe que passe nos 25 testes que o ChatGPT passou, anexando-lhe os registos do ChatGPT:

 

O Gemini falha. Ele adicionou corretamente os testes de ChatGPT. Anexo-lhe os registos da sua execução:

 

Ainda não:

 

Ainda não:

 

Ainda não:

 

Ainda não, mas já está melhor:

 

O Gemini comete novos erros:

 

Está a melhorar novamente:

 

Desta vez, está tudo bem:

Indiscutivelmente, neste exemplo específico do cálculo do imposto de 2019 com as restrições definidas no ficheiro de instruções, o ChatGPT revelou-se mais adequado do que o Gemini. Mas isto é apenas um exemplo.

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

Desta vez, o Gemini gerou um ficheiro MarkDown que eu, posteriormente, transformei em PDF [Le problème selon Gemini version 2]. E o Gemini alterou, de facto, o seu raciocínio:

 
 

Verifica-se que já não existe o cálculo específico do desconto nem a regra de repescagem. O Gemini adotou agora o raciocínio de ChatGPT.