Skip to content

4. 使用 Google Gemini 解决三个问题

我们将提供用于解决这三个问题的三个Gemini会话的截图。我们将进行相当详细的说明。完成这一步后,我们不会再对其他测试的AI重复这一过程。它们的工作方式类似。我们将仅提供最值得注意的细节。

4.1. 引言

请参考前文提供的第一张Gemini截图:

 
  • 在[1]中,Gemini的URL;
  • 在[2]中,所使用的Gemini版本;
  • 在[3-5]中,向Gemini提出的三个问题;

Gemini 是 Google 的一款产品,可通过 URL [https://gemini.google.com/] 访问。如上所示,若要查看问答会话的历史记录,您必须创建一个账户。此外,与所有其他受测的 AI 一样,Gemini 限制了您可以提问的数量以及可以上传的文件数量。 当达到此限制时,会话将结束,并会提供稍后继续的选项。由于在会话中途中断相当令人沮丧,我注册了订阅服务。幸运的是,Gemini订阅的首月是免费的。 对于同样存在此类限制的其他 AI(即 ChatGPT、MistralAI 和 ClaudeAI),我也采取了同样的做法。我注册了一个月的订阅,但在这些情况下,首月是需要付费的。使用 Grok 时我没有遇到任何限制。DeepSeek 虽未明确宣布任何限制,但有时会返回 [服务器繁忙] 的提示并中断会话。这本质上就是一种未明说的限制。

这里开始,我将把问答环节简称为“会话”。AI通常使用英语术语“chat”或“conversation”。

Gemini的提问界面如下:

  • 在 [1] 中,输入您的问题;
  • 在 [2] 处,点击图标即可启动 AI 计算回答;
  • 在 [3-4] 中,您可以附加文件;

4.2. 问题 1

问题 1 的会话如下:

 
  • 在[1]中,问题如下:
  • 在[2]中,Gemini的回答开头;

答案的其余部分如下:

 
 
 
 

答案正确。其余五个人工智能也会以类似的形式给出正确答案。

4.3. 问题 2

4.3.1. 引言

在此,我们回顾[python3-flask-2020]课程中的初始问题。这是在辅导课上给学生的文本。

 

上表使我们能够计算纳税人仅申报工资收入时的简化税额。如注释 (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股;
  • 后续子女每人各贡献1股:

因此,股数为:

  • nbParts=1+nbChildren*0.5+(nbChildren-2)*0.5(若员工未婚);
  • nbParts=2+nbChildren*0.5+(nbChildren-2)*0.5(若已婚);
  • 其中 nbChildren 表示子女数量;
  • 我们计算应税收入 R = 0.9 * S,其中 S 为年薪;
  • 家庭分摊系数 QF 按 QF = R / nbParts 计算
  • 根据以下数据(2019年),我们计算应纳税额 I
9964
0
0
27,519
0.14
1,394.96
73,779
0.3
5,798
156,244
0.4
13,913.69
0
0.45
20163.45

每行包含 3 个字段:field1field2field3。要计算税款 I,我们找出满足 QF <= field1 的第一行,并取该行的数值。例如,对于一名已婚员工,有两个孩子,年薪 S 为 50,000 欧元:

应税收入:R=0.9*S=45,000

份额数:nbParts=2+2*0.5=3

家庭商:QF=45,000/3=15,000

满足 QF <= field1 的第一行如下:

    27519    0.14    1394.96

因此,税款 I 等于 0.14*R – 1394.96*股数=[0.14*45000-1394.96*3]=2115。税款四舍五入至最接近的欧元。

如果第一行满足条件 QF <= field1,则税额为零。

如果 QF 的取值导致条件 QF <= field1 永远无法满足,则使用最后一行中的系数。此处:

    0    0.45    20163.45

由此可得总税额 I = 0.45*R – 20163.45*nbParts

4.3.1.2. 家庭商数上限

 

为确定家庭商数(QF)上限是否适用,我们将重新计算不包含子女的应纳税额。再次以那位有两个孩子、年薪为50,000欧元的已婚雇员为例:

应税收入:R = 0.9 * S = 45,000

分摊人数:nbParts=2(不再计入子女)

家庭商数:QF = 45,000 / 2 = 22,500

满足 QF <= field1 的第一行如下:

    27519    0.14    1394.96

因此,税款 I 等于 0.14*R – 1394.96*股数 = [0.14*45,000 – 1394.96*2] = 3,510

最高子女相关福利:1551 × 2 = 3102 欧元

最低税额:3,510 – 3,102 = 408 欧元

持有2股时的应纳税额(上段已计算为2,115欧元)高于最低税额(408欧元),因此此处不适用家庭上限。

一般而言,税前税额大于 (tax1, tax2),其中:

  • [税1]:包含子女计算的应纳税额;
  • [税1]:指包含子女计算的应纳税额;[税2]:指不包含子女计算的应纳税额,并扣除与子女相关的最高抵免额(此处为每半份1,551欧元);

4.3.1.3. 减税额的计算

 

仍以那位已婚、有两个孩子且年薪为50,000欧元的雇员为例:

上一步计算出的应纳税额(2,115欧元)低于夫妻档的2,627欧元(单身人士为1,595欧元):因此适用减税优惠。其计算方法如下:

税收抵免额 = 免税额(已婚夫妇 = 1,970 / 单身 = 1,196)– 0.75 * 应纳税额

减免额 = 1,970 – 0.75 * 2,115 = 383.75,四舍五入后为384欧元。

新应纳税额 = 2,115 – 384 = 1,731 欧元

计算折扣时必须遵守两条规则(部分AI工具曾在此问题上出错):

  • 折扣金额不能为负数;
  • 折扣金额不得超过已计算的税额;

4.3.1.4. 减税额的计算

 

当金额低于一定门槛时,将对前文计算得出的应纳税额总额减免20%。2019年的门槛如下:

  • 单身:21,037欧元;
  • 已婚夫妇:42,074欧元;(上文示例中使用的37,968欧元这一数值似乎有误);

该起征点将增加以下数值:3,797 *(子女所贡献的半份份额数量)。

再次以那位有两个孩子、年薪S为50,000欧元的已婚雇员为例:

  • 其应税收入(45,000欧元)低于门槛值(42,074 + 2 × 3,797)= 49,668欧元;
  • 因此,他有权享受20%的减税:1,731 * 0.2 = 346.2欧元,四舍五入为347欧元;
  • 纳税人的应纳税额为:1,731 – 347 = 1,384欧元;

4.3.1.5. 应纳税额计算

我们的计算到此结束:应缴净税额为1,384欧元。实际上,纳税人可能还有资格享受其他扣除,特别是向公共或公益组织捐赠的扣除。

4.3.1.6. 高收入人群

前面的例子适用于大多数雇员。然而,高收入者的税款计算方式有所不同。

4.3.1.6.1. 年收入10%减免的上限

在大多数情况下,应税收入按以下公式计算:R = 0.9 × S,其中S为年薪。这被称为10%的减免。该减免设有上限。2019年:

  • 不得超过12,502欧元;
  • 不得低于437欧元;

让我们以一名未婚、无子女且年薪为200,000欧元的雇员为例:

  • 10%的减免额为200,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 × 90,000 – 1,394.96 × 4 = 7,020 欧元;
  • 子女的I2税额:
    • QF = 90,000 / 2 = 45,000 欧元;
    • I2 = 0.3 × 90,000 – 5,798 × 2 = 15,404 欧元;
  • 家庭商数上限规则规定,子女带来的税收优惠不得超过(1,551 × 4个半份额)= 6,204欧元。然而,此处I2 – I1 = 15,404 – 7,020 = 8,384欧元,大于6,204欧元;
  • 因此,应纳税额重新计算为 I3 = I2 - 6,204 = 15,404 - 6,204 = 9,200 欧元;
  • 由于 I3 > I1,将保留税款 I3;

该夫妇既不会获得税收抵免,也不会享受减免,其最终应纳税额为9,200欧元。

4.3.1.7. 官方数据

税款计算较为复杂。在本文档中,将通过以下示例进行测试。结果来源于税务部门的模拟器 |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|:

纳税人
官方结果
本文算法得出的结果
一对夫妇,育有2名子女,年收入55,555欧元
应缴税额 = 2,815欧元
税率 = 14%
税额 = 2,814 欧元
税率 = 14%
一对夫妇,育有2个孩子,年收入50,000欧元
税额 = 1,385 欧元
税收抵免 = 384 欧元
减免额 = 346 欧元
税率 = 14%
应纳税额 = 1,384欧元
减免 = 384 欧元
抵免额 = 347 欧元
税率 = 14%
一对夫妇,育有3个孩子,年收入50,000欧元
税额 = 0 欧元
税收抵免 = 720 欧元
减免 = 0 欧元
税率 = 14%
应纳税额 = 0 欧元
减免 = 720 欧元
扣除额 = 0 欧元
税率 = 14%
单身,有2个孩子,年收入100,000欧元
应纳税额 = 19,884 欧元
税收抵免 = 0 欧元
扣除额 = 0 欧元
税率 = 41%
应纳税额 = 19,884欧元
附加费 = 4,480 欧元
折扣 = 0 欧元
减免 = 0 欧元
税率 = 41%
单身,育有3名子女,年收入100,000欧元
应纳税额 = 16,782 欧元
税收抵免=0欧元
扣除额 = 0 欧元
税率 = 41%
税额 = 16,782 欧元
附加费 = 7,176 欧元
折扣 = 0 欧元
减免 = 0 欧元
税率 = 41%
一对夫妇,育有3名子女,年收入100,000欧元
应缴税款 = 9,200 欧元
税收抵免=0欧元
扣除额 = 0 欧元
税率 = 30%
应纳税额 = 9,200 欧元
附加费 = 2,180 欧元
折扣 = 0 欧元
减免 = 0 欧元
税率 = 30%
一对夫妇,育有5个孩子,年收入100,000欧元
应缴税款 = 4,230 欧元
税收抵免 = 0 欧元
扣除额 = 0 欧元
税率 = 14%
应纳税额 = 4,230 欧元
折扣 = 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欧元
附加费 = 7,498 欧元
折扣=0欧元
减免 = 0 欧元
税率 = 45%
一对夫妇,育有3名子女,年收入200,000欧元
应纳税额 = 42,843 欧元
税收抵免=0欧元
扣除额 = 0 欧元
税率 = 41%
应纳税额 = 42,842欧元
附加费 = 17,283 欧元
折扣=0欧元
减免 = 0 欧元
税率 = 41%

在上例中,“附加费”是指高收入者因以下两个因素而需额外支付的金额:

  • 年度收入10%扣除额的上限;
  • 家庭津贴的上限;

由于税务机关的模拟器未提供该数据,因此无法验证此指标。

我们可以看到,该文档的算法每次都能计算出正确的税额,尽管存在1欧元的误差。该误差源于四舍五入。所有货币金额在某些情况下会被四舍五入至最接近的整数欧元,而在其他情况下则会被四舍五入至最接近的整数欧元。由于我不熟悉官方规则,因此文档算法中的货币金额进行了四舍五入:

  • 折扣和减免金额向上取整至最接近的欧元;
  • 附加费和最终税额向下舍入至最接近的欧元;

我们将要求AI执行此税费计算。

4.3.2. Gemini会话配置

提交给Gemini的问题附带两个文件:

 
  • 在[1]中,上述计算方法已被整理成一份PDF文件,并已提交给Gemini。Gemini可在其中查阅关于2018年收入的2019年税款简化计算的具体规则;
  • 在[2]中,我们的操作指南;
  • 在[3]中,是启动AI的命令;

文本文件 [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/simulateur/calcul_impot/2019/simplifie/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提出的众多问题而形成的。很快就会发现,如果想要得到我们想要的结果,就必须对AI进行非常严格的引导。正是由于这一系列的试错过程,Gemini会话最终因超出限制而被终止。让我们来分析一下这些指令的其余部分:

  • 第1行:我们指定对话应使用法语。此指令针对DeepSeek,因为它倾向于使用英语;
  • 第3行:我们提出的需求;
  • 第5行:我们要求AI使用我们提供的PDF文件;
  • 第 7–14 行:一些有用的提示,特别是针对没有 PDF 的第 3 题。有几个 AI 在税费计算中出了差错;
  • 第15–44行:我们希望包含在生成的脚本中的11个单元测试。脚本生成后,我们将在PyCharm中运行它,并检查这11个测试是否全部通过;
  • 第46–53行:若缺少这些指令,AI生成的单元测试会追求精确结果,从而导致测试失败;
  • 第55–56行:我要求AI不要上网。最简单的解决方案是使用PDF;
  • 第 58–59 行:AI 没有遵循这一指令。当我发现某个测试失败时,不得不将其明确写入提示中;
  • 第 61–65 行:我指定了所需的 Python 脚本类型;
  • 第67–69行:我本希望获得一个链接以获取生成的脚本,因为在屏幕上显示代码需要时间。结果发现大多数AI无法做到这一点。提供的链接均无法使用;
  • 第 71–72 行:我本希望了解 AI 回答问题所花费的时间。只有 Gemini 能提供这一信息。其他 AI 要么对该指令无响应,要么给出任意数字,表明它们未能理解该指令;

4.3.3. Gemini的回复

Gemini的首次回复如下:

 
  • 在 [1-4] 中,Gemini 提供了指向 PDF 或文本文件中包含其当前所用指令的相应部分的链接;

其余内容如下:

 
  • 在[1]中,Gemini声称已成功执行了全部11个单元测试。大多数AI在处理问题2和问题3时都做出了这一声明,但通常在加载生成的脚本后,脚本并不能正常运行。因此,这一声明应持保留态度。然而,对于Gemini而言,这一说法将被证明是正确的;
  • 在[2]中,有一个链接最终被发现无法访问;
  • 在[3]中,仅Gemini提供了合理的执行时间;

因此链接[2]无法访问。我们告诉Gemini:

 

Gemini的回复:

 
  • 在[1]中,由Gemini生成的Python脚本;

我们将该脚本导入 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生成的脚本遵循了文本文件[instructionsWithPDF.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 ---
#  All values are taken from the PDF document supplied.

#  Ceilings for the 10% allowance on annual income
PLAFOND_ABATTEMENT_10_POURCENT = 12502.0
MINIMUM_ABATTEMENT_10_POURCENT = 437.0

#  2019 income tax scale
#  Structure: (Maximum bracket threshold, Tax rate, Amount to be deducted per share)
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}
]

#  Family allowance ceiling
GAIN_MAX_DEMI_PART = 1551.0  #  Maximum gain per additional half-share

#  Thresholds and amounts for the discount
SEUIL_APPLICATION_DECOTE = {"celibataire": 1595.0, "couple": 2627.0}
SEUIL_CALCUL_DECOTE = {"celibataire": 1196.0, "couple": 1970.0}

#  Thresholds for the 20% tax reduction
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
    #  The allowance is capped between a minimum and a maximum
    abattement = min(PLAFOND_ABATTEMENT_10_POURCENT, max(MINIMUM_ABATTEMENT_10_POURCENT, abattement_calcule))

    #  The statement requires the abatement to be rounded up to the nearest euro
    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

    #  If the QF is greater than all the thresholds, the last slice is used
    if tranche_applicable is None:
        tranche_applicable = BAREME_IMPOT_2019[-1]

    #  Gross tax calculation formula
    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

    #  Tax calculation without children's shares
    parts_sans_enfants = calculer_nombre_parts(adultes, 0)
    impot_sans_enfants = calculer_impot_brut_base(revenu_imposable, parts_sans_enfants)

    #  Calculation of maximum child-related earnings
    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

    #  The tax used is the higher of the two calculations
    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
    #  The discount applies if the tax is below the application threshold
    if impot_avant_decote < seuil_application:
        #  Discount formula: threshold - 0.75 * gross tax
        decote_calculee = seuil_calcul - (0.75 * impot_avant_decote)

        #  Rounded up to the nearest euro
        decote = math.ceil(decote_calculee)

        #  The discount cannot be negative or greater than the tax due
        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]

    #  The threshold is increased for each half-percentage point for children
    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
    #  The reduction applies if taxable income is below the threshold
    if revenu_imposable < seuil_final:
        reduction_calculee = impot_avant_reduction * 0.20

        #  Rounded up to the nearest euro
        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.
    """
    #  Step 1: Calculating taxable income
    revenu_imposable = calculer_revenu_imposable(revenus)

    #  Step 2: Calculating the number of shares
    nombre_parts = calculer_nombre_parts(adultes, enfants)

    #  Step 3: Gross tax calculation
    impot_brut_initial = calculer_impot_brut_base(revenu_imposable, nombre_parts)

    #  Step 4: Applying the QF cap
    impot_apres_plafonnement = appliquer_plafonnement_qf(impot_brut_initial, revenu_imposable, adultes, enfants)

    #  Step 5: Applying the discount
    impot_apres_decote, decote_calculee = appliquer_decote(impot_apres_plafonnement, adultes)

    #  Step 6: Apply the 20% discount
    impot_avant_arrondi_final, reduction_calculee = appliquer_reduction_20_pourcent(impot_apres_decote,
                                                                                    revenu_imposable, adultes, enfants)

    #  Step 7: Final tax rounding down to the nearest euro
    impot_final = math.floor(impot_avant_arrondi_final)

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


#  --- TESTS UNITAIRES ---
#  Validation of the script with the cases provided
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)

    #  Example of how to use the calculator for a specific case
    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题完全相同,唯一的区别在于我们不再向AI提供包含计算规则的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]中,要求学生在线查找计算2018年收入的2019年税款的规则。这比前一个练习更难;

下面,我仅提供Gemini首次回答的部分内容:

 
 

预计的时间是合理的。我们等了很久才收到Gemini的回复。

和之前一样,Gemini 提供了一个生成的脚本下载链接,但该链接无法访问。我们告诉它:

 

Gemini 的回复:

 

我们将脚本以 [gemini2] 的名称加载到 PyCharm 中:

 

我们运行它,结果……没能成功。执行日志如下:


"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 POUR L'IMPOSITION 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: If tax is already 0, no discount or reduction.
    if impot_apres_plafonnement == 0:
        return 0, 0, 0

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

    #  1. Determining eligibility for the 20% discount
    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. Determining eligibility for the discount
    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: The discount returned cannot exceed the tax it cancels out
        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. Apply the 20% discount if eligible
    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 文档中未提及的规则。问题出在哪里?我们向 Gemini 咨询,并附上了 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的截图有些麻烦,但我还是这么做了,是为了向读者展示AI的生成能力:

 

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

说实话,我还没核实过这份PDF里的内容是否全部属实。无论如何,这是一份完美的教程文档,仅用几秒钟就生成了。

不过,我们可以让Gemini自己验证这份PDF是否正确。我们开始一段新的对话:

 
  • 在 [1] 中,我们包含了 Gemini 生成的 PDF [The Problem According to Gemini.pdf];
  • 在 [2] 中,[instructionsWithPDF2.txt] 与 [instructionsWithPDF.txt] 中的说明完全相同,只是我们添加了第十二个单元测试——正是这个测试揭示了最初的 PDF 存在错误:
test12 : (2, 2, 49500) -> (1297, 431, 324)

有趣的是,Gemini 经过数次往返迭代后才生成正确的脚本:

问题 2

 

问题3

 

正如之前多次做过的那样,当生成的脚本加载到 PyCharm 后出现故障时,我们会向 Gemini 提供包含执行日志的文本文件。Gemini 能够很好地理解这些日志。

问题 4

 

问题5

 

问题6及结论

我们现在确信Gemini生成的PDF文件是有效的。其中提供的计算规则是正确的。

接下来我们将对另外五款AI进行同样的测试,但除当前AI领域的领军者ChatGPT外,其余的说明将非常简短。我们关注的是AI能否解决我们提出的三个问题。事实上,这些AI的界面非常相似,因此我采用与Gemini相同的操作方式。建议读者自行选择一款AI,重现Gemini的对话过程。