Skip to content

5. Risoluzione dei tre problemi con ChatGPT

5.1. Introduzione

Ecco una prima schermata di una sessione ChatGPT:

 
  • In [1-3], i tre problemi posti in ChatGPT;
  • In [4], il URL di ChatGPT;
  • In [5], la versione di ChatGPT utilizzata;

ChatGPT è un prodotto derivato da OpenAI, disponibile in URL e [https://chatgpt.com/]. Per avere una cronologia delle vostre sessioni di domande e risposte come quella sopra riportata, è necessario creare un account. Inoltre, come tutte le altre versioni testate, IA limita il numero di domande e il numero di file scaricati. Una volta raggiunto tale limite, la sessione termina e viene proposto di riprenderla in un secondo momento. I limiti imposti da ChatGPT vengono raggiunti molto rapidamente. Per realizzare questo tutorial, ho dovuto sottoscrivere un abbonamento a pagamento di un mese.

L'interfaccia di ChatGPT è la seguente:

 
  • In [1], per allegare file alla domanda posta;
  • In [2], la domanda posta;
  • In [3], per avviare l’esecuzione di IA;

5.2. Il problema 1

La domanda in ChatGPT:

  
 

ChatGPT risponde correttamente.

5.3. Problema 2

Si tratta del calcolo dell’imposta con il PDF. Ad essere onesti, useremo il PDF generato da Gemini, che corregge gli errori del PDF iniziale.

 
  • Nel [1] abbiamo inserito il PDF generato da Gemini;
  • Nel file [2] è stato aggiunto il test unitario con cui Gemini ha dimostrato la propria superiorità:
test12 : (2, 2, 49500) -> (1297, 431, 324)

Si avvia ChatGPT. Impiega circa 3 minuti per generare la risposta. A differenza di Gemini, fornisce effettivamente un link funzionante per recuperare lo script generato. Lo si carica in PyCharm:

 

Lo script [chatGPT1] funziona al primo colpo. Non c’è proprio paragone: su questo problema, ChatGPT ha dato risultati migliori rispetto a Gemini.

Lo script [chatGPT1] fornito da ChatGPT è il seguente:

# -*- 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

# ---------- Costanti normative ----------

# Detrazione per spese professionali (soglia minima/massima) per i redditi del 2018
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

# Tabella 2018 (QF)
# Ogni fascia è (borne_sup, aliquota). La prima fascia allo 0% termina a 9 964 €.
BAREME_2018 = [
    (9964, 0.00),
    (27519, 0.14),
    (73779, 0.30),
    (156244, 0.41),
    (float("inf"), 0.45),
]

# Limite massimo del quoziente familiare
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # vantaggio massimo per 1/2 quota ENFANT

# Riduzione (soglie in base al numero di adulti / stato della coppia)
DECOTE_SEUIL_CELIB = 1196
DECOTE_SEUIL_COUPLE = 1970

# Riduzione del 20%: soglie in funzione delle quote
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  # Aggiunta per ogni 1/2 quota oltre la base di 1 o 2 quote


# ---------- Strutture dei dati ----------

@dataclass
class ResultatImpot:
    impot: int  # imposta finale da pagare, arrotondata all’euro inferiore
    decote: int  # sconto arrotondato all’euro superiore
    reduction: int  # riduzione del 20% arrotondata all'euro superiore


# ---------- Funzioni di utilità ----------

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


# ---------- Fase 1: Reddito netto imponibile (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)))


# ---------- Fase 2: Numero di quote ----------

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
     # figli >= 2
    parts += 1.0  # +0,5 +0,5 per i primi due
    if enfants > 2:
        parts += (enfants - 2) * 1.0  # +1 per ogni figlio a partire dal terzo
    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
     # I primi due figli = 2 * mezza quota, poi +2 mezze quote per ogni figlio a partire dal terzo
    return 2 + (enfants - 2) * 2


# ---------- Fase 3: Imposta lorda tramite 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


# ---------- Fase 4.1: Limite massimo del quoziente familiare ----------

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


# ---------- Fase 4.2: Sconto e riduzione del 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

     # Idoneità alla riduzione del 20% basata su RNI
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_red  # "inferiore a una soglia" nel PDF

     # Idoneità allo sconto: direttamente al di sotto della soglia, oppure ripescaggio se la riduzione farebbe scendere al di sotto della soglia
    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  # riduzione teorica del 20%
        if imp_theorique_apres_red <= s_dec:
            repechage = True

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

     # Applicare prima lo sconto (se ammissibile)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
         # Lo sconto non può superare l’imposta residua: viene limitato
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

     # Quindi applicare la riduzione, se ammissibile
    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 principale ----------

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

     # 3) Imposta lorda in base alla tabella (con tutte le quote)
    imp_brut_A = impot_brut_total(rni, parts)

     # 4.1) Limite massimo del quoziente familiare
    imp_apres_plaf = apply_plafonnement_qf(imp_brut_A, rni, adultes, enfants)

     # 4.2) Sconto e successiva riduzione del 20% (con ripescaggio)
    decote, reduction, imp_corrige = calc_decote_reduction(imp_apres_plaf, rni, adultes, parts)

     # 5) Arrotondamento finale dell’imposta (↓) e limitazione a 0
    imp_final = max(0, arrondi_inf(imp_corrige))

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


# ---------- Piccolo programma di prova ----------

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 = [
         # (adulti, bambini, redditi) -> (imposta, sconto, riduzione)
        ((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

Ora chiediamo a ChatGPT di cercare su Internet le regole di calcolo dell’imposta:

 

Questa volta non forniamo il PDF che indicava le regole di calcolo da seguire. Forniamo solo le nostre istruzioni nel file di testo. Ricordiamo che questo file di testo contiene ora 12 test unitari, dopo aver aggiunto a quelli iniziali (11) quello utilizzato da Gemini per dimostrare che il mio PDF iniziale era errato.

ChatGPT risponde in 8 minuti, fornendo un link per scaricare lo script generato. Una volta caricato in PyCharm, questo script supera tutti e 12 i test. Pertanto, alle due domande poste, ChatGPT ha risposto correttamente al primo tentativo, superando così Gemini.

ChatGPT fornisce i propri riferimenti nella risposta:

 

Non c’è altro da dire, è un ottimo lavoro.

Ora possiamo chiedergli, come abbiamo fatto con Gemini, di generare un PDF per gli studenti.

 

La risposta ChatGPT è stata ottenuta dopo diversi tentativi, poiché il PDF generato utilizzava un font che sostituiva i caratteri con un quadrato. Ma alla fine ha generato il PDF. Lo riporto perché fornisce regole diverse rispetto al PDF di Gemini e mi sono quindi chiesto chi avesse ragione. Approfondiremo la questione.

 
 
 

La differenza rispetto al codice PDF di Gemini sta nel calcolo dello sconto. I due codici IA non seguono lo stesso approccio. Gemini aveva scritto:

 
 
 

I due IA presentano due approcci diversi. Chi ha ragione?

5.5. Il problema 4

Chiederemo a ChatGPT di basarsi sul suo PDF per calcolare l’imposta:

Come nelle volte precedenti, genera uno script Python che funziona al primo colpo. Nelle istruzioni avevamo aggiunto un test supplementare:

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

Tutti e 13 i test sono stati superati con successo.

5.6. Ritorno su Gemini

Ora torniamo a Gemini, a cui presenteremo il PDF derivato da ChatGPT. Dato che le regole implementate in questo PDF sono diverse da quelle implementate nel PDF di Gemini, ci si può chiedere cosa succederà:

 

Gemini ha inizialmente generato uno script Python che non superava i test. Le sono stati presentati i log:

Domanda 2

 

Domanda 3

Ci sono ancora degli errori. Continuiamo.

 

Domanda 4

Ci sono ancora degli errori durante l'esecuzione:

 

Questa volta va bene.

Ci incuriosisce comunque il fatto che, pur avendo regole di calcolo piuttosto diverse, sia PDF che IA generino entrambi risultati corretti.

Poniamo la seguente domanda a Gemini:

 

La domanda completa è la seguente:

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.

La risposta di Gemini è categorica:

 
 
 
 
 
 

5.7. Cosa ne pensa ChatGPT

Poniamo a ChatGPT la stessa domanda posta a Gemini.

 

La risposta di ChatGPT è la seguente:

 
 

Di conseguenza, ChatGPT ci propone un test unitario per distinguere i due metodi. Duplichiamo:

  • Lo script [gemini3] generato da Gemini utilizzando come fonte il suo PDF; [Le problème selon Gemini] viene duplicato nello script [gemini4];
  • Lo script [chatGPT3] generato da ChatGPT utilizzando come fonte i propri script PDF e [Le problème selon ChatGPT] viene duplicato nello script [chatGPT4];

Inoltre, in ciascuno degli script [gemini4, chatGPT4] viene aggiunto il test unitario proposto da ChatGPT per distinguere i due IA.

L'esecuzione di [gemini4] fornisce i seguenti risultati:


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

Pertanto, Gemini non supera il test aggiunto da ChatGPT.

L'esecuzione di [chatGPT4] fornisce i seguenti risultati:


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

Anche ChatGPT non supera il test aggiunto, ma non per gli stessi motivi di Gemini. ChatGPT ha trovato i risultati corretti, ma con uno scarto di 2 euro invece dell’1 euro richiesto.

Pertanto, d’ora in poi utilizzeremo il PDF generato da ChatGPT insieme ai successivi IA. Va notato che è proprio a causa della mancanza di test unitari nelle mie istruzioni che entrambi i IA hanno superato i primi test. Da qui, in questo specifico esempio, l’importanza di inserire test unitari per i casi limite del calcolo dell’imposta. Poiché è piuttosto difficile immaginare da soli questi test. Chiederemo ai IA di aggiungerne di propri.

5.8. Il problema 3 con i test unitari generati dai IA

I risultati ottenuti con Gemini e ChatGPT lasciano qualche dubbio. Le IA hanno trovato una soluzione generale che supera tutti i test immaginabili o hanno trovato una soluzione che supera solo i test imposti? Ripartiremo da una soluzione senza PDF per costringere le IA a cercare su Internet le informazioni di cui hanno bisogno. E modifichiamo le nostre istruzioni nel modo seguente:

 

Il file di testo [instructionsSansPDF4.txt] contiene già 14 test obbligatori. A questi test aggiungiamo le seguenti istruzioni:


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.

# =========================
# Test unitari (tolleranza di ±1 €)
# =========================

TESTS = [
     # (adulti, bambini, redditi) -> (imposta, sconto, riduzione)
    ((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()
  • Righe 11-24: i 14 test predefiniti;
  • Righe 5-55: questo codice proviene dallo script generato da ChatGPT. Imporremo a Gemini di utilizzare questo codice per facilitare i confronti tra i due script generati.

Si inizia con ChatGPT:

 

La sua prima risposta è errata. Glielo segnalo fornendogli i log dell’esecuzione:

La sua seconda risposta è corretta. ChatGPT ha aggiunto i seguenti 11 test ai 14 test richiesti:

# Casi limite aggiuntivi (bordi di scatti/arrotondamenti)
TESTS += [
     # Detrazione del 10%: soglia minima e massima
    ((1, 0, 3000), (0, 0, 0)),  # 10% = 300 < soglia minima 437 => RNI basso -> imposta pari a zero
    ((1, 0, 200000), (64211, 0, 0)),  # Tetto massimo della detrazione già coperto nei test iniziali

     # Sconto: appena al di sotto / al di sopra delle soglie
    ((1, 0, 25000), None),  # diagnosi
    ((2, 0, 35000), None),  # diagnosi

     # Riduzione del 20%: diritto pieno vs. limitazione
    ((1, 0, 17000), None),  # diagnosi
    ((2, 0, 34000), None),  # diagnosi
    ((1, 0, 20000), None),  # diagnostica
    ((2, 0, 40000), None),  # diagnostica

     # Modifica delle quote (limite massimo QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Ora ci sono 25 test unitari. Ho verificato manualmente gli 11 nuovi test con il simulatore ufficiale di DGIP e tutto è a posto.

Ora passiamo a Gemini. Sarà molto più complicato. Riuscirà a generare uno script che superi i 25 test di ChatGPT, ma solo dopo un lungo processo di debug.

 

Di seguito, l’elenco del debug:

 

Stranamente, la maggior parte dei test è fallita anche tra i 14 richiesti, mentre in passato Gemini aveva generato codice in grado di superarli tutti.

La seguente risposta di Gemini non è ancora corretta:

 

E nemmeno la risposta seguente:

 

E nemmeno la risposta seguente. A questo punto cambio strategia. Gli chiedo di superare i 25 test che ChatGPT ha superato, allegando i log di ChatGPT:

 

Gemini fallisce. Ha comunque aggiunto i test di ChatGPT. Gli allego i log della sua esecuzione:

 

Ancora niente:

 

Ancora niente:

 

Ancora niente:

 

Ancora no, ma va meglio:

 

Gemini commette nuovi errori:

 

Sta migliorando di nuovo:

 

Questa volta è tutto a posto:

Indubbiamente, in questo specifico esempio di calcolo dell’imposta 2019 con i vincoli specificati nel file delle istruzioni, ChatGPT si è dimostrato più efficace di Gemini. Ma si tratta solo di un esempio.

Si può andare oltre. Si può chiedere a Gemini di rigenerare un PDF secondo le regole di calcolo che ha utilizzato per superare i 25 test. Vogliamo vedere se ha modificato il suo ragionamento iniziale sui calcoli dello sconto e della riduzione del 20%:

Questa volta, Gemini ha generato un file MarkDown che ho poi trasformato in PDF [Le problème selon Gemini version 2]. E Gemini ha effettivamente modificato il proprio ragionamento:

 
 

Si nota che non è più presente né il calcolo specifico dello sconto né la regola di ripescaggio. Gemini ha ora adottato il ragionamento di ChatGPT.