calculette_online.py 3.65 KB
Newer Older
1
2
3
4
5
6
7
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Use finances.gouv.fr web simulator as an API to compute income taxes.

Based on a script by Adrien Fabre in [OpenFisca-France](https://github.com/openfisca/openfisca-france).
8
9
10

Example:
python3 calculette_impots/scripts/calculette_online.py --saisie 1AJ=10000 1BJ=20000 0DA=1980 0DB=1970 0AX=05052014 pre_situation_famille=M
11
12
13
"""


Christophe Benz's avatar
Christophe Benz committed
14
import argparse
15
import json
Christophe Benz's avatar
Christophe Benz committed
16
import re
17
18
19
import sys

from lxml import etree
Christophe Benz's avatar
Christophe Benz committed
20
21
22
23
24
from toolz import merge
import requests


parser = None
25
26
27


def iter_results(root_node):
Christophe Benz's avatar
Christophe Benz committed
28
29
30
31
    ignored_input_hidden_names = (
        'blanc',  # white line only used for presentation
        'mesgouv2',  # explanations text block
        )
32
    for element in root_node.xpath('//input[@type="hidden"][@name]'):
Christophe Benz's avatar
Christophe Benz committed
33
34
35
        element_name = element.get('name').strip()
        if element_name in ignored_input_hidden_names:
            continue
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
        parent = element.getparent()
        parent_tag = parent.tag.lower()
        if parent_tag == 'table':
            tr = parent[parent.index(element) - 1]
            assert tr.tag.lower() == 'tr', tr
        else:
            assert parent_tag == 'tr', parent_tag
            tr = parent
        while True:
            description = etree.tostring(tr[1], encoding=str, method='text').strip().rstrip(u'*').strip()
            if description:
                break
            table = tr.getparent()
            tr = table[table.index(tr) - 1]
        yield {
Christophe Benz's avatar
Christophe Benz committed
51
            'name': element_name,
52
53
54
55
56
            'description': description,
            'value': float(element.get('value').strip()),
            }


Christophe Benz's avatar
Christophe Benz committed
57
58
59
60
61
62
63
64
65
66
67
def iter_saisie_variables(values):
    for value, words in map(lambda value: (value, value.strip('=').split('=', 1)), values):
        if len(words) == 1:
            parser.error('Missing value for variable saisie: "{}"'.format(value))
        elif len(words) > 2:
            parser.error('Invalid syntax for variable saisie: "{}"'.format(value))
        variable_name, variable_value = words
        variable_value = str(variable_value)
        yield variable_name, variable_value


68
def main():
Christophe Benz's avatar
Christophe Benz committed
69
70
71
72
73
74
75
76
77
    global parser
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--output-html', help='Output result page HTML to file')
    parser.add_argument('--saisies', dest='saisie_variables', metavar='nom=valeur', nargs='+', help='Variables saisies')
    parser.add_argument('--year', default='2015', type=int,
                        help='Calculer les impôts de l\'année N sur les revenus de l\'année N-1')
    args = parser.parse_args()

    cgi_url = 'http://www3.finances.gouv.fr/cgi-bin/calc-{}.cgi'.format(args.year)
78
    headers = {'User-Agent': 'Calculette-Impots-Python'}
Christophe Benz's avatar
Christophe Benz committed
79
80
81
82
    saisie_variables = {} if args.saisie_variables is None else dict(iter_saisie_variables(args.saisie_variables))
    default_saisie_variables = {
        # '0DA': '1965',
        # '1AJ': '15000',
83
84
85
86
        'pre_situation_famille': 'C',
        'pre_situation_residence': 'M',
        # 'simplifie': '1',
        }
Christophe Benz's avatar
Christophe Benz committed
87
    data = merge(default_saisie_variables, saisie_variables)
88
    response = requests.post(cgi_url, headers=headers, data=data)
Christophe Benz's avatar
Christophe Benz committed
89
90
91
92
93
94
95
    if args.output_html is not None:
        with open(args.output_html, 'w') as output_html_file:
            output_html_file.write(re.sub(
                pattern=r'=(.)/calcul_impot/2015/',
                repl=r'=\1http://www3.finances.gouv.fr/calcul_impot/2015/',
                string=response.text,
                ))
96
97
98
99
100
101
102
103
104
    root_node = etree.fromstring(response.text, etree.HTMLParser())
    results = list(iter_results(root_node))
    print(json.dumps(results, ensure_ascii=False, indent=2, sort_keys=True))

    return 0


if __name__ == "__main__":
    sys.exit(main())