Skip to content

4. Вирішення трьох задач за допомогою Google Gemini

Ми наведемо скріншоти трьох сесій Gemini, які дозволили вирішити три поставлені проблеми. Ми розглянемо їх досить детально. Після цього ми не будемо повторювати цей процес для інших протестованих IA. Вони працюють аналогічно. Ми наведемо лише найважливіші деталі.

4.1. Вступ

Нагадаємо перший знімок екрана Gemini, наведений раніше:

 
  • У [1] — URL від Gemini;
  • У [2] — версія Gemini, що використовувалася;
  • У [3-5] — три задачі, поставлені Gemini;

Gemini — це продукт Google, доступний за посиланням URL [https://gemini.google.com/]. Щоб отримати історію ваших сеансів запитань/відповідей, як показано вище, вам потрібно створити обліковий запис. Крім того, як і всі інші протестовані IA, Gemini обмежує кількість ваших запитань та кількість завантажених файлів. Коли цей ліміт вичерпано, сесія завершується, і вам пропонується продовжити її пізніше. Оскільки досить прикро зупинятися посеред сесії, я оформив передплату. На щастя, перший місяць передплати на Gemini безкоштовний. Я вчинив так само з іншими сервісами, що мали такі обмеження, а саме: IA, ChatGPT, MistralAI, ClaudeAI. Я оформив місячну підписку, але в цьому випадку перший місяць був платним. З Grok я не зіткнувся з жодними обмеженнями. DeepSeek не заявляє про обмеження, але іноді відповідає [Server busy] і перериває сесію. Це все одно, що встановлювати обмеження, не кажучи про це.

Далі я говоритиму про сесію запитань і відповідей, яку для стислості називатиму просто «сесією». У IA найчастіше використовується англійський термін «chat» (обговорення) або «conversation» (розмова).

Інтерфейс Gemini для постановки запитання виглядає так:

  • У [1] — ваше запитання;
  • У полі [2] — піктограма, яка запускає IA для обчислення відповіді;
  • У [3-4] ви можете додавати файли;

4.2. Задача 1

Сесія для задачі 1 виглядає так:

 
  • У [1] — запитання;
  • У [2] — початок відповіді Gemini;

Продовження відповіді таке:

 
 
 
 

Відповідь правильна. П’ять інших IA також дадуть правильну відповідь у подібній формі.

4.3. Задача 2

4.3.1. Вступ

Нагадаємо початкову задачу курсу [python3-flask-2020]. Це текст, який студентам надано у форматі TD.

 

Наведена вище таблиця дозволяє розрахувати податок у спрощеному випадку, коли платник податків декларує лише свою заробітну плату. Як зазначено у примітці (1), розрахований таким чином податок є податком до застосування трьох механізмів:

  • обмеження сімейного коефіцієнта, яке застосовується до високих доходів;
  • знижки та зменшення податку, що застосовуються до низьких доходів;

Отже, розрахунок податку включає такі етапи [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

 

Пропонується написати програму, яка дозволить розрахувати податок платника податків за 2019 рік у спрощеному випадку, коли платник податків декларує лише свою заробітну плату.

4.3.1.1. Розрахунок валового податку

Валовий податок можна розрахувати таким чином:

Спочатку обчислюємо кількість часток платника податків:

  • Кожен з батьків дає 1 частку;
  • двоє перших дітей приносять по 1/2 частки;
  • Наступні діти приносять по одній частці кожен:

Отже, кількість часток становить:

  • nbParts=1+nbEnfants*0,5+(nbEnfants-2)*0,5, якщо працівник не одружений;
  • nbParts = 2 + nbEnfants * 0,5 + (nbEnfants - 2) * 0,5, якщо він одружений;
  • де nbEnfants — кількість дітей;
  • Обчислюємо оподатковуваний дохід R = 0,9 * S, де S — річна заробітна плата;
  • Розраховується сімейний коефіцієнт QF = R / nbParts;
  • Розраховуємо валовий податок I на основі таких даних (2019 рік):
9964
0
0
27519
0,14
1394,96
73779
0,3
5798
156244
0,4
13913,69
0
0,45
20163,45

Кожен рядок має 3 поля: champ1, champ2, champ3. Щоб обчислити податок I, шукаємо перший рядок, у якому QF <= поле1, і беремо значення з цього рядка. Наприклад, для працівника, який перебуває у шлюбі, має двох дітей і отримує річну заробітну плату S у розмірі 50000 євро:

Оподатковуваний дохід: R = 0,9 * S = 45000

Кількість часткок: nbParts = 2 + 2 * 0,5 = 3

Сімейний коефіцієнт: QF = 45 000 / 3 = 15 000

Перший рядок, у якому QF <= поле1, має такий вигляд:

    27519    0.14    1394.96

Тоді податок I дорівнює 0,14*R – 1394,96*nbParts = [0,14*45000-1394,96*3] = 2115. Податок округлюється до найближчого нижчого євро.

Якщо вже в першому рядку виконується умова QF <= поле1, то податок дорівнює нулю.

Якщо QF таке, що нерівність QF <= поле1 ніколи не виконується, то використовуються коефіцієнти з останнього рядка. У даному випадку:

    0    0.45    20163.45

що дає валовий податок I = 0,45*R – 20163,45*nbParts.

4.3.1.2. Обмеження сімейного коефіцієнта

 

Щоб з’ясувати, чи застосовується обмеження сімейного коефіцієнта QF, потрібно перерахувати валовий податок без урахування дітей. Знову ж таки, для працівника, який перебуває у шлюбі, має двох дітей та отримує річну заробітну плату S у розмірі 50000 євро:

Оподатковуваний дохід: R = 0,9 * S = 45 000

Кількість часток: nbParts = 2 (дітей більше не враховують)

Сімейний коефіцієнт: QF = 45 000 / 2 = 22 500

Перший рядок, де QF <= поле 1, виглядає так:

    27519    0.14    1394.96

Податок I дорівнює 0,14*R – 1394,96*nbParts=[0,14*45000-1394,96*2]=3510.

Максимальний дохід, пов’язаний з дітьми: 1551 * 2 = 3102 євро

Мінімальний податок: 3510 – 3102 = 408 євро

Валовий податок із 2 частками, вже розрахований у попередньому абзаці (2115 євро), перевищує мінімальний податок (408 євро), тому сімейне обмеження в цьому випадку не застосовується.

Загалом, валовий податок є більшим за (податок1, податок2), де:

  • [impôt1] — валовий податок, розрахований з урахуванням дітей;
  • [impôt2] — валовий податок, розрахований без урахування дітей та зменшений на максимальну суму пільги (у даному випадку 1551 євро за півчастини), пов’язану з дітьми;

4.3.1.3. Розрахунок знижки

 

Знову ж таки, для працівника, який перебуває у шлюбі, має двох дітей та отримує річну заробітну плату S у розмірі 50 000 євро:

Валовий податок (2115 євро), отриманий на попередньому етапі, є меншим за 2627 євро для подружжя (1595 євро для одинокої особи): отже, застосовується знижка. Її розраховують за такою формулою:

знижка = поріг (для подружжя = 1970 / для одинокої особи = 1196) – 0,75 * валовий податок

знижка = 1970 – 0,75 × 2115 = 383,75, округлено до 384 євро.

Новий валовий податок = 2115 – 384 = 1731 євро

При розрахунку знижки слід дотримуватися двох правил (деякі інструменти IA зіткнулися з цією проблемою):

  • Знижка не може бути від’ємною;
  • Знижка не може перевищувати вже розрахований податок;

4.3.1.4. Розрахунок знижки з податку

 

Нижче певного порогу до валового податку, отриманого в результаті попередніх розрахунків, застосовується знижка у розмірі 20 %. У 2019 році пороги є такими:

  • одинокі особи: 21037 євро;
  • для подружжя: 42 074 євро; (цифра 37 968, використана у наведеному вище прикладі, здається помилковою);

Цей поріг збільшується на величину: 3797 * (кількість півчастин, що припадають на дітей).

Знову ж таки, для працівника, який перебуває у шлюбі, має двох дітей та отримує річну заробітну плату S у розмірі 50 000 євро:

  • Його оподатковуваний дохід (45 000 євро) є меншим за поріг (42 074 + 2 × 3797) = 49 668 євро;
  • Отже, він має право на зменшення податку на 20 %: 1731 * 0,2 = 346,2 євро, округлено до 347 євро;
  • Валовий податок платника податків становить: 1731 – 347 = 1384 євро;

4.3.1.5. Розрахунок чистого податку

На цьому наш розрахунок завершується: чистий податок до сплати становитиме 1384 євро. Насправді платник податків може скористатися іншими пільгами, зокрема за пожертви на користь організацій, що діють в інтересах суспільства або загального блага.

4.3.1.6. Випадок високих доходів

Наш попередній приклад відповідає більшості випадків, що стосуються найманих працівників. Однак розрахунок податку відрізняється у випадку високих доходів.

4.3.1.6.1. Обмеження 10-відсоткової знижки на річний дохід

У більшості випадків оподатковуваний дохід обчислюється за формулою: R = 0,9 * S, де S — річна заробітна плата. Це називається 10-відсотковим зниженням. Для цього зниження встановлено верхню межу. У 2019 році:

  • вона не може перевищувати 12 502 євро;
  • вона не може бути меншою за 437 євро;

Розглянемо випадок неодруженого працівника без дітей із річною заробітною платою 200 000 євро:

  • Зниження на 10 % становить 20 000 євро > 12 502 євро. Отже, воно зменшується до 12 502 євро;

4.3.1.6.2. Обмеження сімейного коефіцієнта

Розглянемо випадок, коли застосовується обмеження сімейного коефіцієнта, описане в розділі |Обмеження сімейного коефіцієнта|. Розглянемо випадок подружжя з трьома дітьми та річним доходом 100 000 євро. Повторимо етапи розрахунку:

  • Знижка у розмірі 10 % становить 100 000 євро < 12 502 євро. Отже, оподатковуваний дохід R дорівнює 100 000 – 10 000 = 90 000 євро;
  • Пара має nbParts = 2 + 0,5 × 2 + 1 = 4 частки;
  • Отже, їхній сімейний коефіцієнт становить QF = R / nbParts = 90 000 / 4 = 22 500 євро;
  • Їхній валовий податок I1 з урахуванням дітей становить I1=0,14*90000-1394,96*4= 7020 євро;
  • Його валовий податок I2 без дітей:
    • QF = 90 000 / 2 = 45 000 євро;
    • I2 = 0,3 × 90 000 – 5 798 × 2 = 15 404 євро;
  • Правило обмеження сімейного коефіцієнта передбачає, що виграш, який приносять діти, не може перевищувати (1551 × 4 півчастини) = 6204 євро. Однак у даному випадку він становить I2-I1 = 15404 – 7020 = 8384 євро, тобто перевищує 6204 євро;
  • Отже, валовий податок перераховується як I3 = I2-6204 = 15404 – 6204 = 9200 євро;
  • Оскільки I3 > I1, то буде збережено податок I3;

Ця подружня пара не отримає ані знижки, ані пільги, і її остаточний податок становитиме 9200 євро.

4.3.1.7. Офіційні цифри

Розрахунок податку є складним. У цьому документі розрахунки проводитимуться на таких прикладах. Результати наведено за даними симулятора податкової адміністрації |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|:

Платник податків
Офіційні результати
Результати алгоритму, наведеного в документі
Пара з двома дітьми та річним доходом 55555 євро
Податок = 2815 євро
Ставка податку = 14 %
Податок = 2814 євро
Ставка оподаткування = 14 %
Пара з двома дітьми та річним доходом 50 000 євро
Податок = 1385 євро
Знижка = 384 євро
Знижка = 346 євро
Ставка податку = 14 %
Податок = 1384 євро
Знижка = 384 євро
Знижка = 347 євро
Ставка податку = 14 %
Пара з трьома дітьми та річним доходом 50 000 євро
Податок = 0 євро
Знижка = 720 євро
Знижка = 0 євро
Ставка оподаткування = 14 %
Податок = 0 євро
знижка = 720 євро
Знижка = 0 євро
Ставка податку = 14 %
Одинока особа з 2 дітьми та річним доходом 100 000 євро
Податок = 19 884 євро
Знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Податок = 19 884 євро
Надбавка = 4480 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Одинока особа з 3 дітьми та річним доходом 100 000 євро
Податок = 16 782 євро
Знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Податок = 16 782 євро
Надбавка = 7176 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Подружжя з трьома дітьми та річним доходом 100 000 євро
Податок = 9200 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 30 %
Податок = 9200 євро
Надбавка = 2180 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 30 %
Пара з 5 дітьми та річним доходом 100 000 євро
Податок = 4230 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 14 %
Податок = 4230 євро
знижка = 0 євро
Знижка = 0 євро
Ставка податку = 14 %
Одинока особа без дітей із річним доходом 100 000 євро
Податок = 22 986 євро
Знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Податок = 22 986 євро
Надбавка = 0 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Подружжя з двома дітьми та річним доходом 30 000 євро
Податок = 0 євро
Знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 0 %
Податок = 0 євро
знижка = 0 євро
Знижка = 0 євро
Ставка податку = 0 %
Одинока особа без дітей із річним доходом 200 000 євро
Податок = 64 211 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 45 %
Податок = 64 210 євро
Надбавка = 7498 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 45 %
Подружжя з трьома дітьми та річним доходом 200 000 євро
Податок = 42 843 євро
Знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %
Податок = 42 842 євро
Націнка = 17 283 євро
знижка = 0 євро
Знижка = 0 євро
Ставка оподаткування = 41 %

У наведеному вище прикладі надбавкою називають суму, яку особи з високими доходами сплачують додатково через два фактори:

  • обмеження розміру податкової пільги 10 % від річного доходу;
  • обмеження сімейного коефіцієнта;

Цей показник не вдалося перевірити, оскільки симулятор податкової адміністрації його не надає.

Видно, що алгоритм, наведений у документі, щоразу обчислює правильну суму податку, однак із похибкою в 1 євро. Ця похибка зумовлена округленням. Усі суми іноді округлюються до вищого, іноді — до нижчого євро. Оскільки я не знав офіційних правил, грошові суми в алгоритмі документа були округлені:

  • до вищого євро — для знижок та пільг;
  • до нижчого євро — для надбавок та остаточного податку;

Ми попросимо IA виконати цей розрахунок податку.

4.3.2. Налаштування сесії Gemini

Запит до Gemini супроводжується двома файлами:

 
  • У файлі [1] розрахунок, який щойно було детально описано, було поміщено у файл PDF, який передається Gemini. Gemini знайде там точні правила спрощеного розрахунку податку за 2019 рік на доходи за 2018 рік;
  • У файлі [2] — наші інструкції;
  • У файлі [3] — для запуску файлу IA;

Наші інструкції у текстовому файлі [instructionsAvecPDF.txt] такі:

1 - Exprime-toi en français.

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

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

4 - Tu dois faire attention aux points suivants :

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

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

Dans ces tests on appelle :

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

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

On utilise la syntaxe (adultes, enfants, revenus) -> (impot, decote, reduction) pour dire que le script reçoit les entrées
(adultes, enfants, revenus) et produit les résultats (impot, decote, reduction)

test1 : (2,2,55555) -> (2815, 0, 0)
test2 : (2, 2, 50000) -> (1385, 384, 346)
test3 : (2,3,50000) -> (0, 720, 0)
test4: (1,2,100000) -> (19884, 0, 0)
test5: (1,3,100000) -> (16782, 0, 0)
test6 : (2, 3, 100000) -> (9200, 0, 0)
test7 : (2, 5, 100000) -> (4230, 0, 0)
test8 : (1, 0, 100000) -> (22986, 0, 0)
test9 : (2, 2, 30000) -> (0, 0, 0)
test10 : (1,0,200000) -> (64211, 0, 0)
test11  : (2, 3, 200000) -> (42843, 0, 0)

6 - Il peut y avoir des problèmes d'arrondis. Tu procèderas comme suit
- l'impôt à payer sera arrondi à l'euro inférieur,
- la décote sera arrondie à l'euro supérieur,
- la réduction de 20 % sera arrondie à l'euro supérieur.
- l'abattement de 10% sera arrondi à l'euro supérieur

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

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

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

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

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

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

Ці інструкції є результатом численних запитань, поставлених Gemini. Досить швидко стає зрозуміло, що IA має бути дуже чітко сформульованим, якщо ми хочемо отримати бажане. Саме через усі ці спроби та помилки сесія Gemini була зрештою зупинена через перевищення лімітів. Розглянемо подальшу частину цих інструкцій:

  • Рядок 1: вимагається, щоб розмова велася французькою мовою. Ця вказівка стосується DeepSeek, який мав схильність говорити англійською;
  • Рядок 3: те, чого ми хочемо;
  • Рядок 5: IA наказують використовувати PDF, який йому надали;
  • Рядки 7–14: низка корисних порад, особливо для задачі 3 без PDF. Кілька IA загубилися під час обчислення податку;
  • Рядки 15–44: 11 модульних тестів, які ми хочемо включити до згенерованого скрипта. Коли скрипт буде згенеровано, ми запустимо його в PyCharm і перевіримо, чи пройдуть усі 11 тестів;
  • Рядки 46–53: без цих інструкцій IA генерував би модульні тести, що шукають точні результати, які б завершувалися невдачею;
  • Рядки 55–56: я вказую IA не виходити в Інтернет. Найпростішим рішенням є використання PDF;
  • Рядки 58–59: ця інструкція не була виконана IA. Мені довелося явно вказати це у запиті, коли я помітив, що тест завершився невдало;
  • Рядки 61–65: я вказую, який тип скрипта Python мені потрібен;
  • Рядки 67–69: я б волів отримати посилання для завантаження згенерованого скрипта, оскільки відображення коду на екрані займає багато часу. Виявилося, що більшість IA не вміють цього робити. Надані посилання не працювали;
  • Рядки 71–72: я хотів би знати, скільки часу знадобилося IA для відповіді на запитання. Лише Gemini змогла надати мені цю інформацію. Інші IA або не виконували цю інструкцію, або наводили вигадані цифри, що свідчило про те, що вони не зрозуміли її;

4.3.3. Відповідь Gemini

Перша відповідь Gemini така:

 
  • У [1-4] Gemini надає посилання на ту частину PDF або текстового файлу з інструкціями, яку він використовує в даний момент;

Далі йде таке:

 
  • У [1] Gemini стверджує, що успішно виконав 11 модульних тестів. Більшість IA стверджували це як щодо задачі 2, так і щодо задачі 3, і часто при завантаженні згенерованого скрипта він не працював. Тому до цього твердження слід ставитися з обережністю. Для Gemini це виявиться правдою;
  • У [2] міститься посилання, яке, як виявиться, не працює;
  • У [3] лише Gemini надав реалістичний час виконання;

Отже, посилання [2] не працює. Повідомляємо про це Gemini:

 

Відповідь Gemini:

 
  • У [1] — скрипт Python, згенерований Gemini;

Завантажуємо цей скрипт у PyCharm і запускаємо його:

 
  • У файлі [1] файл [gemini1] є скриптом, згенерованим Gemini;

Під час виконання скрипта з’являються помилки компіляції:


"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py" 
Testing started at 17:12 ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours

Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm 2025.2.0.1\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py", line 38, in <module>
    sys.exit(main(argv=args, module=None, testRunner=unittestpy.TeamcityTestRunner,
             ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  buffer=not JB_DISABLE_BUFFERING))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 103, in __init__
    self.parseArgs(argv)
    ~~~~~~~~~~~~~~^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 142, in parseArgs
    self.createTests()
    ~~~~~~~~~~~~~~~~^^
  File "C:\Program Files\Python313\Lib\unittest\main.py", line 153, in createTests
    self.test = self.testLoader.loadTestsFromNames(self.testNames,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
                                                   self.module)
                                                   ^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\loader.py", line 207, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
              ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Program Files\Python313\Lib\unittest\loader.py", line 137, in loadTestsFromName
    module = __import__(module_name)
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py", line 28, in <module>
    [cite_start]
     ^^^^^^^^^^
NameError: name 'cite_start' is not defined

Process finished with exit code 1

Empty suite
  • Рядок 30, помилка компіляції. [cite_start] — це маркер для генерації певного формату тексту;

Ми поміщаємо наведені вище логи у файл [logs gemini1.txt] і передаємо його Gemini:

 

Відповідь Gemini виглядає так:

 

Після завантаження в файл PyCharm виконання нового скрипта призводить до точно такої самої помилки. Про це повідомляємо Gemini, знову надаючи йому логи виконання:

 

Відповідь Gemini така:

 

Цього разу все гаразд. Усі 11 модульних тестів пройшли успішно. Про це повідомляємо Gemini:

 

На що він відповідає:

 

Скрипт, згенерований Gemini, виконав інструкції, наведені у текстовому файлі [instructionsAvecPDF.txt]:

# -*- coding: utf-8 -*-
"""
Script pour le calcul de l'impôt 2019 sur les revenus de 2018 en France,
basé sur les règles simplifiées fournies dans le document PDF.
"""

import math
import unittest

# --- CONSTANTES DE CALCUL POUR L'IMPÔT 2019 ---
# Усі значення взято з наданого документа PDF.

# Граничні суми для 10-відсоткового вирахування з річного доходу
PLAFOND_ABATTEMENT_10_POURCENT = 12502.0
MINIMUM_ABATTEMENT_10_POURCENT = 437.0

# Шкала податку на доходи за 2019 рік
# Структура: (Максимальний поріг діапазону, Ставка оподаткування, Сума, що підлягає вирахуванню на одну частку)
BAREME_IMPOT_2019 = [
    {"seuil": 9964, "taux": 0.0, "deduction_par_part": 0},
    {"seuil": 27519, "taux": 0.14, "deduction_par_part": 1394.96},
    {"seuil": 73779, "taux": 0.30, "deduction_par_part": 5798},
    {"seuil": 156244, "taux": 0.41, "deduction_par_part": 13913.69},
    {"seuil": float('inf'), "taux": 0.45, "deduction_par_part": 20163.45}
]

# Максимальний розмір сімейного коефіцієнта
GAIN_MAX_DEMI_PART = 1551.0  # Максимальний прибуток за кожну додаткову половину частки

# Пороги та суми для знижки
SEUIL_APPLICATION_DECOTE = {"celibataire": 1595.0, "couple": 2627.0}
SEUIL_CALCUL_DECOTE = {"celibataire": 1196.0, "couple": 1970.0}

# Порогові значення для 20-відсоткового зниження податку
SEUIL_BASE_REDUCTION_20 = {"celibataire": 21037.0, "couple": 42074.0}
MAJORATION_SEUIL_REDUCTION_PAR_DEMI_PART = 3797.0


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

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

    return parts


def calculer_revenu_imposable(revenus: float) -> float:
    """
    Calcule le revenu net imposable après l'abattement de 10%.
    Cet abattement est plafonné.
    L'abattement est arrondi à l'euro supérieur.
    """
    abattement_calcule = revenus * 0.1
     # Розмір податкової пільги обмежується мінімальним та максимальним значеннями
    abattement = min(PLAFOND_ABATTEMENT_10_POURCENT, max(MINIMUM_ABATTEMENT_10_POURCENT, abattement_calcule))

     # У завданні вимагається округлити пільгу до найближчого вищого євро
    abattement_arrondi = math.ceil(abattement)

    return revenus - abattement_arrondi


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

    quotient_familial = revenu_imposable / nombre_parts

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

     # Якщо значення QF перевищує всі порогові значення, використовується останній діапазон
    if tranche_applicable is None:
        tranche_applicable = BAREME_IMPOT_2019[-1]

     # Формула розрахунку валового податку
    impot_brut = (revenu_imposable * tranche_applicable["taux"]) - (
            tranche_applicable["deduction_par_part"] * nombre_parts)

    return max(0.0, impot_brut)


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

     # Розрахунок податку без урахування частки дітей
    parts_sans_enfants = calculer_nombre_parts(adultes, 0)
    impot_sans_enfants = calculer_impot_brut_base(revenu_imposable, parts_sans_enfants)

     # Розрахунок максимального прибутку, пов’язаного з дітьми
    parts_avec_enfants = calculer_nombre_parts(adultes, enfants)
    demi_parts_enfants = (parts_avec_enfants - parts_sans_enfants) * 2
    gain_maximal = demi_parts_enfants * GAIN_MAX_DEMI_PART

    impot_minimal_plafone = impot_sans_enfants - gain_maximal

     # Утриманий податок дорівнює більшому з двох розрахункових значень
    return max(impot_brut_avec_enfants, impot_minimal_plafone)


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

    decote = 0.0
     # Знижка застосовується, якщо податок не перевищує порогове значення
    if impot_avant_decote < seuil_application:
         # Формула знижки: поріг — 0,75 * валовий податок
        decote_calculee = seuil_calcul - (0.75 * impot_avant_decote)

         # Округлення до найближчого євро в більшу сторону
        decote = math.ceil(decote_calculee)

         # Знижка не може бути від’ємною або перевищувати суму податку
        decote = max(0.0, min(decote, impot_avant_decote))

    impot_apres_decote = impot_avant_decote - decote
    return impot_apres_decote, decote


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

     # Поріг збільшується на кожну половину частки, пов’язану з дітьми
    parts_avec_enfants = calculer_nombre_parts(adultes, enfants)
    parts_adultes = calculer_nombre_parts(adultes, 0)
    demi_parts_enfants = (parts_avec_enfants - parts_adultes) * 2
    seuil_final = seuil_base + (demi_parts_enfants * MAJORATION_SEUIL_REDUCTION_PAR_DEMI_PART)

    reduction = 0.0
     # Знижка застосовується, якщо оподатковуваний дохід не перевищує порогове значення
    if revenu_imposable < seuil_final:
        reduction_calculee = impot_avant_reduction * 0.20

         # Округлення до найближчого євро
        reduction = math.ceil(reduction_calculee)

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


def calculer_impot(adultes: int, revenus: float, enfants: int) -> dict:
    """
    Orchestre le calcul complet de l'impôt net à payer.
    """
     # Етап 1: Розрахунок оподатковуваного доходу
    revenu_imposable = calculer_revenu_imposable(revenus)

     # Етап 2: Розрахунок кількості паїв
    nombre_parts = calculer_nombre_parts(adultes, enfants)

     # Етап 3: Розрахунок суми податку до оподаткування
    impot_brut_initial = calculer_impot_brut_base(revenu_imposable, nombre_parts)

     # Етап 4: Застосування обмеження QF
    impot_apres_plafonnement = appliquer_plafonnement_qf(impot_brut_initial, revenu_imposable, adultes, enfants)

     # Етап 5: Застосування знижки
    impot_apres_decote, decote_calculee = appliquer_decote(impot_apres_plafonnement, adultes)

     # Етап 6: Застосування знижки у розмірі 20 %
    impot_avant_arrondi_final, reduction_calculee = appliquer_reduction_20_pourcent(impot_apres_decote,
                                                                                    revenu_imposable, adultes, enfants)

     # Крок 7: Остаточне округлення податку до найближчого нижчого євро
    impot_final = math.floor(impot_avant_arrondi_final)

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


# --- TESTS UNITAIRES ---
# Перевірка роботи скрипта на наведених прикладах
class TestCalculImpot2019(unittest.TestCase):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

     # Приклад використання калькулятора для конкретного випадку
    print("\n--- Exemple de calcul ---")
    revenus_annuels = 50000
    nombre_adultes = 2
    nombre_enfants = 2

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

    print(f"Pour un couple ({nombre_adultes} adultes) avec {nombre_enfants} enfants et {revenus_annuels}€ de revenus :")
    print(f"  - Impôt à payer : {resultat_calcul['impot']}€")
    print(f"  - Montant de la décote : {resultat_calcul['decote']}€")
    print(f"  - Montant de la réduction : {resultat_calcul['reduction']}€")

Я не перевіряв цей код. Оскільки 11 модульних тестів пройшли успішно, я вважаю, що він «ймовірно правильний». Щодо власного коду я не робив нічого, крім перевірки цих 11 тестів.

4.4. Завдання 3

Задача 3 ідентична задачі 2, за винятком того, що IA більше не отримує PDF, який містив правила обчислення, яких слід дотримуватися.

Початкове запитання до Gemini таке:

 

Файл інструкцій [1] практично не відрізняється від файлу для задачі 2, за винятком таких відмінностей:


1 - Exprime-toi en français.

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

3 - Tu t'aideras des sources que tu trouveras sur internet. Dans ta réponse indique-moi ces sources.

4 - Tu dois faire attention aux points suivants :

  • У завданні [3] йому пропонується знайти в Інтернеті правила розрахунку податку за 2019 рік на доходи за 2018 рік. Це завдання складніше за попереднє;

Нижче я наводжу лише уривки з першої відповіді Gemini:

 
 

Орієнтовний час виконання є правдоподібним. На відповідь від Gemini доводиться довго чекати.

Як і раніше, Gemini надав посилання для завантаження згенерованого скрипта, яке не працює. Ми йому про це повідомили:

 

Відповідь Gemini:

 

Завантажуємо скрипт у PyCharm під назвою [gemini2]:

 

Виконуємо його і… нічого не виходить. Журнали виконання виглядають так:


"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py" 
Testing started at 17:23 ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours


Failure
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 278, in test_cas_2
    self.assertAlmostEqual(impot, 1385, delta=1)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1691 != 1385 within 1 delta (306 difference)


Error
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 291, in test_cas_3
    impot, _, _ = calculer_impot_final(2, 3, 50000)
                  ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculer_impot_final
    decote, impot_apres_decote = calculer_decote(impot_apres_plafonnement, adultes)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculer_decote
    decote = seuil_decote - (impot_avant_decote * COFFICIENT_DECOTE)
                                                  ^^^^^^^^^^^^^^^^^
NameError: name 'COFFICIENT_DECOTE' is not defined. Did you mean: 'COEFFICIENT_DECOTE'?


Error
Traceback (most recent call last):
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 316, in test_cas_9
    self._verifier_cas(2, 2, 30000, (0, 0, 0))
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 216, in _verifier_cas
    impot_calcule, decote_calculee, reduction_calculee = calculer_impot_final(adultes, enfants, revenus)
                                                         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 187, in calculer_impot_final
    decote, impot_apres_decote = calculer_decote(impot_apres_plafonnement, adultes)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", line 134, in calculer_decote
    decote = seuil_decote - (impot_avant_decote * COFFICIENT_DECOTE)
                                                  ^^^^^^^^^^^^^^^^^
NameError: name 'COFFICIENT_DECOTE' is not defined. Did you mean: 'COEFFICIENT_DECOTE'?



Ran 11 tests in 0.038s

FAILED (failures=1, errors=2)

Process finished with exit code 1
  • Рядок 11, тест не пройшов;
  • Рядки 25, 42: одна й та сама помилка компіляції;

Ці журнали ми записуємо у текстовий файл, який передаємо Gemini:

 

Відповідь Gemini:

 
 

Завантажуємо скрипт у PyCharm і запускаємо його. Знову помилки. Повідомляємо про це Gemini, знову додавши логи виконання:

 

Відповідь від Gemini:

 
 
 

Завантажуємо цей новий скрипт у PyCharm і запускаємо його. Цього разу всі 11 модульних тестів пройшли успішно:

Код, згенерований Gemini, виглядає так:

# -*- coding: utf-8 -*-
import math
import unittest

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


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

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


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


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


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


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

     # CORRECTION: Якщо податок уже дорівнює 0, знижки чи зменшення не застосовуються.
    if impot_apres_plafonnement == 0:
        return 0, 0, 0

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

     # 1. Визначити право на знижку в розмірі 20%
    est_eligible_reduction = False
    if adultes >= 2:
        seuil_rfr = SEUIL_RFR_REDUCTION_COUPLE + max(0, (nombre_parts - 2) * 2) * MAJORATION_RFR_REDUCTION_PAR_DEMI_PART
    else:
        seuil_rfr = SEUIL_RFR_REDUCTION_CELIBATAIRE + max(0, (
                nombre_parts - 1) * 2) * MAJORATION_RFR_REDUCTION_PAR_DEMI_PART
    if revenu_imposable < seuil_rfr:
        est_eligible_reduction = True

     # 2. Визначити право на знижку
    seuil_decote_foyer = SEUIL_DECOTE_COUPLE if adultes >= 2 else SEUIL_DECOTE_CELIBATAIRE
    reduction_theorique = math.ceil(impot_a_traiter * TAUX_REDUCTION_20_POURCENT) if est_eligible_reduction else 0

    if (impot_a_traiter - reduction_theorique) < seuil_decote_foyer:
        decote_calculee = seuil_decote_foyer - (impot_a_traiter * COEFFICIENT_DECOTE)
        decote_calculee = math.ceil(decote_calculee)
         # CORRECTION: Сума поверненої знижки не може перевищувати суму податку, яку вона компенсує
        decote_calculee = min(decote_calculee, math.ceil(impot_a_traiter))
        impot_a_traiter -= decote_calculee
        impot_a_traiter = max(0.0, impot_a_traiter)

     # 3. Застосувати знижку у розмірі 20%, якщо є право на неї
    if est_eligible_reduction:
        reduction_calculee = math.ceil(impot_a_traiter * TAUX_REDUCTION_20_POURCENT)
        impot_a_traiter -= reduction_calculee
        impot_a_traiter = max(0.0, impot_a_traiter)

    impot_final = math.floor(impot_a_traiter)

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


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

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

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

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

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

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

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

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

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

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

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

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

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


if __name__ == '__main__':
    print("Lancement des tests unitaires pour le calcul de l'impôt 2019...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

Знову ж таки, я не перевіряв цей код. Я просто переконався, що він успішно пройшов усі 11 тестів.

Але може бути цікаво дізнатися, як саме він працює, зокрема щодо окремих випадків розрахунку податку. Запитуємо у нього:

 

Це випадок високих доходів, у якому можливе одночасне обмеження податкової пільги на 10 % та обмеження сімейного коефіцієнта.

Відповідь Gemini така:

 
 
 
 
 

Ці два останні знімки екрана є цікавими. Gemini використовує метод обчислення, відмінний від того, що описано в PDF. Цей метод обчислення дійсно можна знайти в Інтернеті. Обидва методи є еквівалентними.

 
 
 

Пояснення вирізняється своєю зрозумілістю. Його можна було б надати студентам у незмінному вигляді для пояснення методу розрахунку податку.

Тепер розглянемо інший приклад, цього разу з низькими доходами. У цьому випадку можливі знижки та зменшення:

 

Відповідь Gemini така:

 
 
 
 
 

Тут можна помітити, що Gemini застосовує правило, якого немає в PDF. Ймовірно, він знайшов його в Інтернеті, але чи є це джерело надійним?

 

Тут Gemini продовжує застосовувати невідоме правило (згадане вище спеціальне правило).

 
 
 

Отже, результати Gemini збігаються з результатом офіційного податкового симулятора. Але він використав правило, якого немає в PDF. Де помилка? Ми запитуємо про це, додавши файл PDF:

 

Відповідь Gemini:

 
 
 
 

Я вважаю, що Gemini має рацію, а мій PDF є помилковим. Щоб це перевірити, я прошу його провести тест:

  • У якому його розрахунки дадуть ті самі результати, що й офіційний податковий симулятор;
  • де розрахунки за кодом PDF дають результати, відмінні від результатів симулятора;
 

Відповідь Gemini така:

 

Тут Gemini помиляється. Я запустив симулятор на цьому прикладі й отримав такі результати:

 

Однак ми побачимо, що міркування Gemini дійсно дають наведені вище результати. Продовжуємо:

 
 
 
 

Дуже добре. Зазначено. Продовжуємо:

 
 
 
 

Отже, Gemini отримав (податок, знижка, зменшення) = (431, 325, 1296), тоді як симулятор, яким я скористався, дає (431, 324, 1297). Отже, Gemini отримав правильні результати з похибкою в 1 євро, але він про це не знає. Ми йому це повідомляємо:

 

Gemini відповідає:

 
 

Тепер виникає питання, чи міг би Gemini згенерувати виправлений PDF:

 

Відповідь Gemini:

 

Отже, Gemini не надав мені посилання на PDF, але згенерував текст, за допомогою якого я сам зміг створити цей PDF. Хоча розміщувати тут скріншоти PDF дещо незручно, я роблю це, щоб читач побачив генеративний аспект IA:

 

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Чесно кажучи, я не перевіряв, чи все, що сказано в цьому PDF, є правдою. У будь-якому разі, це ідеальний документ для TD, згенерований за лічені секунди.

Проте можна попросити самого Gemini перевірити, чи його PDF є правильним. Починаємо нову розмову:

 
  • у [1] ми вставили PDF, згенерований Gemini [Le problème selon Gemini.pdf];
  • у [2] [instructionsAvecPDF2.txt] ідентичний інструкціям [instructionsAvecPDF.txt], за винятком того, що було додано дванадцятий модульний тест — саме той, який показав, що початковий PDF був помилковим:
test12 : (2, 2, 49500) -> (1297, 431, 324)

Цікаво, що знадобилося кілька спроб, перш ніж Gemini згенерував правильний скрипт:

Питання 2

 

Питання 3

 

Як це вже робилося кілька разів, коли скрипт, згенерований і завантажений у PyCharm, дає збій, ми надаємо Gemini текстовий файл з логами виконання. Gemini їх чудово розуміє.

Питання 4

 

Питання 5

 

Питання 6 та кінець

Тепер ми переконалися у правильності коду PDF, згенерованого Gemini. Наведені в ньому правила обчислення є правильними.

Тепер ми зробимо те саме для п’яти інших IA, але наші пояснення будуть дуже стислими, за винятком ChatGPT та IA, який наразі є лідером. Нас цікавить, чи вирішує IA три поставлені перед ним завдання. Справді, інтерфейси всіх цих IA дуже схожі, і я працював з ними так само, як і з Gemini. Читачеві пропонується повторити діалоги Gemini з IA на свій вибір.