Skip to content

5. Rozwiązanie trzech problemów za pomocą ChatGPT

5.1. Wprowadzenie

Oto pierwszy zrzut ekranu z sesji ChatGPT:

 
  • W [1-3] pojawiły się trzy problemy zgłoszone w ChatGPT;
  • W sesji [4] pojawia się wynik z sesji URL, który pochodzi z sesji ChatGPT;
  • W [5] zastosowano wersję z pliku ChatGPT;

ChatGPT jest produktem opartym na OpenAI, dostępnym w URL i [https://chatgpt.com/]. Aby uzyskać historię swoich sesji pytań i odpowiedzi, jak pokazano powyżej, należy utworzyć konto. Ponadto, podobnie jak wszystkie inne przetestowane wersje IA, ChatGPT ogranicza liczbę zadawanych pytań oraz liczbę pobranych plików. Po osiągnięciu tego limitu sesja zostaje zakończona i pojawia się propozycja jej kontynuowania w późniejszym terminie. Limity narzucone przez ChatGPT są osiągane bardzo szybko. Aby przygotować ten poradnik, musiałem wykupić miesięczną płatną subskrypcję.

Interfejs ChatGPT wygląda następująco:

 
  • W [1], aby dołączyć pliki do zadanego pytania;
  • W [2] – zadane pytanie;
  • W [3], aby uruchomić wykonanie IA;

5.2. Zadanie 1

Pytanie w pliku ChatGPT:

  
 

ChatGPT udziela prawidłowej odpowiedzi.

5.3. Problem 2

Chodzi o obliczenie podatku przy użyciu pliku PDF. Szczerze mówiąc, wykorzystamy plik PDF wygenerowany przez Gemini, który koryguje błędy zawarte w pierwotnym pliku PDF.

 
  • W pliku [1] podaliśmy plik PDF wygenerowany przez Gemini;
  • W pliku [2] dodano test jednostkowy, dzięki któremu Gemini wykazało swoją przewagę:
test12 : (2, 2, 49500) -> (1297, 431, 324)

Uruchamiamy ChatGPT. Wygenerowanie odpowiedzi zajmuje mu około 3 minut. W przeciwieństwie do Gemini, dostarcza on działający link umożliwiający pobranie wygenerowanego skryptu. Wczytujemy go do PyCharm:

 

Skrypt [chatGPT1] działa za pierwszym razem. Nie ma co do tego wątpliwości – w tym przypadku ChatGPT poradził sobie lepiej niż Gemini.

Skrypt [chatGPT1] dostarczony przez ChatGPT wygląda następująco:

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

# ---------- Stałe wartości regulacyjne ----------

# Odliczenie kosztów zawodowych (minimum/maksimum) dla dochodów z 2018 r.
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

# Tabela podatkowa na rok 2018 (QF)
# Każdy przedział wynosi (borne_sup, stawka). Pierwszy przedział 0% kończy się na kwocie 9 964 €.
BAREME_2018 = [
    (9964, 0.00),
    (27519, 0.14),
    (73779, 0.30),
    (156244, 0.41),
    (float("inf"), 0.45),
]

# Limit ilorazu rodzinnego
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # maksymalna ulga na 1/2 udziału ENFANT

# Obniżka (progi w zależności od liczby osób dorosłych / statusu związku)
DECOTE_SEUIL_CELIB = 1196
DECOTE_SEUIL_COUPLE = 1970

# Obniżka o 20%: progi w zależności od udziałów
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  # dodatek za każdą dodatkową połowę udziału powyżej podstawy wynoszącej 1 lub 2 udziały


# ---------- Struktury danych ----------

@dataclass
class ResultatImpot:
    impot: int  # ostateczna kwota podatku do zapłaty, zaokrąglona w dół do najbliższego euro
    decote: int  # ulga zaokrąglona w górę do pełnego euro
    reduction: int  # 20% zniżka zaokrąglona w górę do pełnego euro


# ---------- Funkcje pomocnicze ----------

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


# ---------- Krok 1: Dochód netto podlegający opodatkowaniu (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)))


# ---------- Krok 2: Liczba udziałów ----------

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
     # dzieci >= 2
    parts += 1.0  # +0,5 +0,5 za dwoje pierwszych
    if enfants > 2:
        parts += (enfants - 2) * 1.0  # +1 na dziecko od trzeciego
    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 części
     # Dwoje pierwszych dzieci = 2 * pół części, a następnie +2 pół części na każde kolejne dziecko, począwszy od trzeciego
    return 2 + (enfants - 2) * 2


# ---------- Krok 3: Podatek brutto za pomocą 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


# ---------- Krok 4.1: Ograniczenie ilorazu rodzinnego ----------

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


# ---------- Krok 4.2: Odliczenie i ulga w wysokości 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

     # Uprawnienie do 20-procentowej ulgi na podstawie RNI
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_red  # „poniżej progu” w PDF

     # Uprawnienie do obniżki: bezpośrednio poniżej progu lub kwalifikacja uzupełniająca, jeśli zniżka spowodowałaby zejście poniżej progu
    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  # teoretyczna obniżka 20%
        if imp_theorique_apres_red <= s_dec:
            repechage = True

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

     # Najpierw zastosować obniżkę (jeśli kwalifikuje się)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
         # Ulga nie może przekroczyć pozostałego podatku: jest ograniczona
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

     # Następnie zastosować ulgę, jeśli przysługuje
    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 główna ----------

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

     # 3) Podatek brutto według stawki (z uwzględnieniem wszystkich udziałów)
    imp_brut_A = impot_brut_total(rni, parts)

     # 4.1) Ograniczenie ilorazu rodzinnego
    imp_apres_plaf = apply_plafonnement_qf(imp_brut_A, rni, adultes, enfants)

     # 4.2) Odliczenie, a następnie obniżka o 20% (z uwzględnieniem kwoty rezerwowej)
    decote, reduction, imp_corrige = calc_decote_reduction(imp_apres_plaf, rni, adultes, parts)

     # 5) Ostateczne zaokrąglenie podatku (↓) i ograniczenie do 0
    imp_final = max(0, arrondi_inf(imp_corrige))

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


# ---------- Mały plik wykonywalny do testów ----------

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 = [
         # (dorośli, dzieci, dochody) -> (podatek, obniżka, ulga)
        ((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. Problem 3

Teraz prosimy ChatGPT o wyszukanie w Internecie zasad obliczania podatku:

 

Tym razem nie podajemy pliku PDF, który zawierał zasady obliczeń, których należało przestrzegać. W pliku tekstowym podajemy jedynie nasze instrukcje. Przypominamy, że ten plik tekstowy zawiera obecnie 12 testów jednostkowych po dodaniu do 11 początkowych testów tego, który wykorzystał Gemini, aby wykazać, że mój pierwotny plik PDF był błędny.

ChatGPT odpowiada w ciągu 8 minut, podając link do pobrania wygenerowanego skryptu. Po załadowaniu do PyCharm skrypt ten przechodzi wszystkie 12 testów. Zatem na oba postawione zadania ChatGPT odpowiedział poprawnie za pierwszym razem, przewyższając tym samym Gemini.

ChatGPT podaje źródła w swojej odpowiedzi:

 

Nie ma co dodawać, to świetna robota.

Teraz możemy poprosić go, tak jak zrobiliśmy to w przypadku Gemini, o wygenerowanie pliku PDF dla studentów.

 

Odpowiedź ChatGPT uzyskano po kilku rundach wymiany, ponieważ wygenerowany plik PDF wykorzystywał czcionkę, która zastępowała znaki kwadratem. Ostatecznie jednak wygenerował plik PDF. Podaję go, ponieważ zawiera on inne reguły niż plik PDF z Gemini i zacząłem się zastanawiać, kto ma rację. Zbadamy tę sprawę.

 
 
 

Różnica w stosunku do modelu PDF firmy Gemini polega na sposobie obliczania dyskonta. Oba modele IA mają różne podejście. Firma Gemini napisała:

 
 
 

Oba kody IA mają dwa różne podejścia. Kto ma rację?

5.5. Zadanie 4

Poprosimy ChatGPT, aby na podstawie swojego PDF obliczył podatek:

Podobnie jak poprzednio, generuje on skrypt w języku Python, który działa za pierwszym razem. W instrukcjach dodaliśmy dodatkowy test:

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

Wszystkie 13 testów zakończyło się sukcesem.

5.6. Powrót do Gemini

Teraz wracamy do Gemini, któremu przedstawimy plik PDF z ChatGPT. Ponieważ reguły zaimplementowane w tym PDF różnią się od tych zaimplementowanych w PDF w Gemini, można się zastanawiać, co się stanie:

 

Gemini najpierw wygenerowało skrypt w języku Python, który nie przeszedł testów. Przedstawiono mu logi:

Pytanie 2

 

Pytanie 3

Wciąż występują błędy. Kontynuujemy.

 

Pytanie 4

Wciąż pojawiają się błędy podczas wykonywania:

 

Tym razem wszystko działa poprawnie.

Nadal jednak intryguje nas fakt, że mimo dość różnych reguł obliczeniowych w PDF, oba IA generują poprawne wyniki.

Zadajemy Gemini następujące pytanie:

 

Pełne brzmienie pytania jest następujące:

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.

Odpowiedź Gemini jest jednoznaczna:

 
 
 
 
 
 

5.7. Co o tym sądzi ChatGPT

Zadajemy ChatGPT to samo pytanie, które zadaliśmy Gemini.

 

Odpowiedź ChatGPT brzmi następująco:

 
 

W związku z tym ChatGPT proponuje nam test jednostkowy, aby rozstrzygnąć, która z metod jest lepsza. Powielamy:

  • Skrypt [gemini3] wygenerowany przez Gemini, przyjmując jako źródło jego PDF [Le problème selon Gemini] jest powielony w skrypcie [gemini4];
  • Skrypt [chatGPT3], wygenerowany przez ChatGPT na podstawie skryptów źródłowych PDF i [Le problème selon ChatGPT], jest duplikowany w skrypcie [chatGPT4];

Ponadto do każdego ze skryptów [gemini4, chatGPT4] dodaje się test jednostkowy zaproponowany przez ChatGPT w celu rozstrzygnięcia sporu między dwoma skryptami IA.

Wykonanie skryptu [gemini4] daje następujące wyniki:


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

Zatem Gemini nie przechodzi testu dodanego przez ChatGPT.

Uruchomienie pliku [chatGPT4] daje następujące wyniki:


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

ChatGPT również nie przechodzi dodanego testu, ale nie z tych samych powodów co Gemini. ChatGPT uzyskał prawidłowe wyniki, ale z odchyleniem o 2 euro zamiast wymaganych 1 euro.

W związku z tym od tej pory będziemy używać PDF wygenerowanego przez ChatGPT wraz z kolejnymi IA. Należy zauważyć, że to właśnie z powodu braku testów jednostkowych w moich instrukcjach oba IA przeszły pomyślnie pierwsze testy. Stąd w tym konkretnym przykładzie wynika znaczenie wprowadzenia testów jednostkowych dla skrajnych przypadków obliczania podatku. Ponieważ samodzielne wymyślenie takich testów jest raczej trudne. Poprosimy więc pliki IA, aby same je dodały.

5.8. Problem 3 z testami jednostkowymi wygenerowanymi przez IA

Wyniki uzyskane przy użyciu Gemini i ChatGPT budzą wątpliwości. Czy IA znalazły ogólne rozwiązanie, które spełnia wszystkie możliwe do wyobrażenia testy, czy też znalazły rozwiązanie, które spełnia jedynie narzucone testy? Zaczniemy od nowa, rezygnując z PDF, aby zmusić IA do wyszukania w Internecie potrzebnych informacji. Modyfikujemy nasze instrukcje w następujący sposób:

 

Plik tekstowy [instructionsSansPDF4.txt] zawiera już 14 testów obowiązkowych. Do tych testów dodajemy następujące instrukcje:


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.

# =========================
# Testy jednostkowe (tolerancja ±1 €)
# =========================

TESTS = [
     # (osoby dorosłe, dzieci, dochody) -> (podatek, ulga, obniżka)
    ((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()
  • Wiersze 11–24: 14 testów obowiązkowych;
  • Wiersze 5–55: ten kod pochodzi ze skryptu wygenerowanego przez ChatGPT. Zmusimy Gemini do użycia tego kodu, aby ułatwić porównania między dwoma wygenerowanymi skryptami.

Zaczynamy od ChatGPT:

 

Jego pierwsza odpowiedź jest nieprawidłowa. Informuję go o tym, przekazując mu logi z wykonania:

Jego druga odpowiedź jest poprawna. ChatGPT dodał 11 poniższych testów do 14 wymaganych:

# Dodatkowe przypadki graniczne (granice przedziałów/zaokrąglenia)
TESTS += [
     # Odliczenie 10%: dolna i górna granica
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < próg minimalny 437 => RNI niski -> zerowy podatek
    ((1, 0, 200000), (64211, 0, 0)),  # górna granica ulgi uwzględniona już w początkowych testach

     # Obniżka: tuż poniżej / powyżej progów
    ((1, 0, 25000), None),  # diagnoza
    ((2, 0, 35000), None),  # diagnoza

     # 20-procentowa obniżka: pełne prawo vs ograniczenie
    ((1, 0, 17000), None),  # diagnoza
    ((2, 0, 34000), None),  # diagnostyka
    ((1, 0, 20000), None),  # diagnostyka
    ((2, 0, 40000), None),  # diagnostyka

     # Zmiana udziałów (ograniczenie QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Obecnie mamy 25 testów jednostkowych. Ręcznie sprawdziłem 11 nowych testów za pomocą oficjalnego symulatora DGIP i wszystko działa poprawnie.

Teraz przechodzimy do Gemini. To będzie znacznie bardziej skomplikowane. Uda mu się wygenerować skrypt, który przejdzie wszystkie 25 testów ChatGPT, ale dopiero po długim debugowaniu.

 

Poniżej znajduje się lista wyników debugowania:

 

Co dziwne, większość testów zakończyła się niepowodzeniem, nawet wśród 14 wymaganych, podczas gdy w przeszłości Gemini generowało kod, który przechodził je wszystkie.

Poniższa odpowiedź Gemini nadal nie jest poprawna:

 

Następująca odpowiedź również nie jest poprawna:

 

Kolejna odpowiedź również nie jest poprawna. W związku z tym zmieniam taktykę. Proszę go o pomyślne przejście 25 testów, które zaliczył ChatGPT, dołączając logi z ChatGPT:

 

Gemini nie radzi sobie. Dodano wprawdzie testy z ChatGPT. Załączam logi z jego wykonania:

 

Nadal nie działa:

 

Nadal nie działa:

 

Wciąż nie:

 

Wciąż nie, ale już lepiej:

 

Gemini popełnia nowe błędy:

 

Znowu jest lepiej:

 

Tym razem wszystko działa poprawnie:

Bez wątpienia w tym konkretnym przykładzie obliczenia podatku za rok 2019, z uwzględnieniem ograniczeń zawartych w pliku instrukcji, ChatGPT okazał się bardziej trafny niż Gemini. Ale to tylko przykład.

Można pójść o krok dalej. Można poprosić Gemini o ponowne wygenerowanie pliku PDF zgodnie z zasadami obliczeniowymi, które zastosowało, aby pomyślnie przejść 25 testów. Chcemy sprawdzić, czy zmieniło swoje pierwotne rozumowanie dotyczące obliczeń obniżki i 20-procentowej ulgi:

Tym razem Gemini wygenerowało plik o nazwie MarkDown, który następnie przekształciłem w PDF i [Le problème selon Gemini version 2]. Gemini rzeczywiście zmieniło swoje rozumowanie:

 
 

Widać, że nie ma już ani specjalnego obliczenia dyskonta, ani reguły ponownego rozpatrywania. Gemini przyjęło teraz sposób rozumowania z ChatGPT.