Commit d4079236 authored by Christophe Benz's avatar Christophe Benz

WIP

parent 88bf4db1
# -*- coding: utf-8 -*-
from toolz import get_in
from toolz import get_in, keyfilter
def find_restituee_variables():
from .generated.variables_definitions import variable_definition_by_name
return keyfilter(is_restituee_variable, variable_definition_by_name).keys()
def get_variable_applications(variable_name):
return get_variable_definition(variable_name, {}).get('applications')
def get_variable_definition(variable_name, default=None):
......@@ -9,24 +18,39 @@ def get_variable_definition(variable_name, default=None):
return variable_definition_by_name.get(variable_name, default)
def get_variable_description(variable_name):
return get_variable_definition(variable_name, {}).get('description')
def get_variable_type(variable_name):
"""Return variable type given its name or None if `variable_name` is not a variable."""
return get_variable_definition(variable_name, {}).get('type')
def has_tag(tag, variable_name):
return tag in get_in(['attributes', 'tags'], get_variable_definition(variable_name, {}), default=[])
def is_constant(variable_name):
from .generated import constants
return variable_name in constants.__dict__
def is_base_variable(variable_name):
return is_calculee_variable(variable_name) and \
'base' in get_in(['attributes', 'tags'], get_variable_definition(variable_name, {}), default=[])
return is_calculee_variable(variable_name) and has_tag('base', variable_name)
def is_calculee_variable(variable_name):
return get_variable_type(variable_name) == 'variable_calculee'
def is_restituee_variable(variable_name):
return is_calculee_variable(variable_name) and has_tag('restituee', variable_name)
def is_saisie_variable(variable_name):
return get_variable_type(variable_name) == 'variable_saisie'
def is_variable_in_application(variable_name, application_name):
return application_name in get_variable_definition(variable_name, {}).get('applications', [])
......@@ -3,6 +3,14 @@
import math
from m_language_parser import dependencies_helpers
from toolz.curried import first
from . import core
"""Functions used by formulas"""
# M language functions
......@@ -29,3 +37,55 @@ def present(value):
somme = sum
# Cache decorator
# FIXME Do not use a global variable.
result_by_formula_name_cache = {}
def cache_result(formula_function):
formula_name = formula_function.__name__
def new_formula_function():
if formula_name not in result_by_formula_name_cache:
result = formula_function()
result_by_formula_name_cache[formula_name] = result
return result_by_formula_name_cache[formula_name]
return new_formula_function
def inspect():
ok_variable_tuples = inspect_cache(predicate=lambda x: x == 2461)
ok_variable_names = map(first, ok_variable_tuples)
first_ok_variable_name = first(ok_variable_names)
# import ipdb; ipdb.set_trace()
return find_restituee_variables_depending_on(first_ok_variable_name)
def inspect_cache(predicate=None, restituee=False):
return [
(
formula_name,
result,
core.get_variable_description(formula_name),
core.get_variable_applications(formula_name),
)
for formula_name, result in result_by_formula_name_cache.items()
if ((not restituee or core.is_restituee_variable(formula_name)) and
# core.is_variable_in_application(formula_name, 'batch') and
(predicate(result) if predicate is not None else True))
]
def find_restituee_variables_depending_on(formula_name):
restituee_variables = core.find_restituee_variables()
for restituee_variable in restituee_variables:
formula_dependencies = []
dependencies_helpers.build_formula_dependencies(
formula_dependencies=formula_dependencies,
formula_name=formula_name,
)
return formula_dependencies
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -15,7 +15,7 @@ import pprint
import textwrap
from m_language_parser.unloop_helpers import iter_unlooped_nodes
from toolz import interpose, mapcat
from toolz import concatv, interpose, mapcat
from . import core
......@@ -44,15 +44,15 @@ def visit_infix_expression(node, operators={}):
else operators.get(operand_or_operator, operand_or_operator)
for index, operand_or_operator in interleave(node['operands'], node['operators'])
]
# Detect a specific case and transform it into a lazy expression in order to prevent a division by 0:
# a * b ... => a and a * b ...
# Transform product expressions into a lazy "and" expression in order to prevent a division by 0:
if node['type'] == 'product_expression':
# last_operand_type = node['operands'][-1]['type']
# assert last_operand_type in ('function_call', 'integer', 'sum_expression', 'symbol'), last_operand_type
# if last_operand_type != 'integer':
tokens = interpose(
el='and',
seq=[visit_node(operand) for operand in node['operands'][:-1]] + [' '.join(map(str, tokens))],
tokens = concatv(
interpose(
el='and',
seq=map(visit_node, node['operands'][:-1]),
),
['and'],
tokens,
)
return '({})'.format(' '.join(map(str, tokens)))
......@@ -140,12 +140,22 @@ def visit_float(node):
def visit_formula(node):
sanitized_formula_name = sanitized_variable_name(node['name'])
formula_name = node['name']
sanitized_formula_name = sanitized_variable_name(formula_name)
expression_source = visit_node(node['expression'])
source = '{} = {}'.format(sanitized_formula_name, expression_source)
if 'pour_formula' in node:
pour_formula_name = node['pour_formula']['name']
source = '# From {}\n'.format(pour_formula_name) + source
source = """\
@cache_result
def {}():
\"\"\"
{}{}
\"\"\"
return {}
""".format(
sanitized_formula_name,
core.get_variable_description(formula_name),
'\n From {}'.format(node['pour_formula']['name']) if 'pour_formula' in node else '',
expression_source,
)
return (sanitized_formula_name, source)
......@@ -211,7 +221,11 @@ def visit_symbol(node):
else (
'base_variables[{!r}]'.format(symbol)
if core.is_base_variable(symbol)
else sanitized_variable_name(symbol)
else (
'{}()'.format(sanitized_variable_name(symbol))
if core.is_calculee_variable(symbol)
else sanitized_variable_name(symbol)
)
)
......
......@@ -7,12 +7,13 @@ import logging
import os
import sys
from toolz.curried import itemmap, keyfilter, pipe
from toolz.curried import drop, map, pipe, take_nth
from toolz.curried.operator import attrgetter
from calculateur_impots import core
from calculateur_impots.generated import formulas
from calculateur_impots.generated.variables_definitions import variable_definition_by_name
from calculateur_impots.generated.verif_regles import verif_regles
# from calculateur_impots.generated.verif_regles import verif_regles
# Globals
......@@ -25,14 +26,6 @@ log = logging.getLogger(script_name)
script_dir_name = os.path.dirname(os.path.abspath(__file__))
def iter_variables_calculees(variables_names):
for variable_name in variables_names:
variable_type = core.get_variable_type(variable_name)
if variable_type != 'variable_calculee':
parser.error('Variable "{}" is not a variable calculee'.format(variable_name))
yield variable_name
def iter_saisie_variables(values):
for value, words in map(lambda value: (value, value.strip('=').split('=', 1)), values):
if len(words) == 1:
......@@ -55,7 +48,8 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Increase output verbosity')
parser.add_argument('-d', '--debug', action='store_true', default=False, help='Display debug messages')
parser.add_argument('calculees', default=['IINET'], metavar='variable', nargs='*', help='Variables calculées')
parser.add_argument('--calculee', dest='calculee_variable', default='IINETIR', metavar='variable',
help='Variables calculées')
parser.add_argument('--saisie', dest='saisie_variables', metavar='nom=valeur', nargs='+', help='Variables saisies')
args = parser.parse_args()
logging.basicConfig(
......@@ -63,32 +57,50 @@ def main():
stream=sys.stdout,
)
if not core.is_restituee_variable(args.calculee_variable):
parser.error('Variable "{}" is not a variable of type "calculee restituee"'.format(args.calculee_variable))
# Required variables_saisies: V_ANREV, V_REGCO (tag "contexte"?)
# Set V_IND_TRAIT to "primitif" (value 0?)
# src/tgvH.m:968:APPLI_AVIS : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 restituee alias AVIS : "Application Avis" ; # noqa
# src/tgvH.m:970:APPLI_COLBERT : saisie contexte classe = 0 priorite = 0 categorie_TL = 0 restituee alias V_COLBERT : "Appli_Colbert" type BOOLEEN ; # noqa
# src/tgvH.m:973:APPLI_OCEANS : saisie contexte classe = 0 priorite = 0 categorie_TL = 0 restituee alias V_OCEANS : "Appli_Oceans" ; # noqa
# src/tgvH.m:2299:CODE_2042 : saisie contexte classe = 0 priorite = 0 categorie_TL = 0 alias V_CODE_2042 : "nouv cor: code majoration de la 2042 fourni par l interface" ; # noqa
# src/tgvH.m:5470:ITREDFRI : saisie contexte classe = 0 priorite = 0 categorie_TL = 0 restituee alias V_REDFRI : "ITRED de la decla finale avant application 1731 bis " type REEL ; # noqa
# src/tgvH.m:14401:V_ANCSDED : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias ANCSDED : "Annee de revenu pour variation CSG" ; # noqa
# src/tgvH.m:14404:V_ANREV : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias ANREV : "Annee des revenus" type REEL ; # noqa
# src/tgvH.m:14826:V_ROLCSG : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias ROLCSG : "numero de role CSG" ; # noqa
# src/tgvH.m:14523:V_CALCULIR : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias CALCULIR : "= 0 si saisie 2042 ILIAD , = 1 si CALCULIR sous ILIAD" ; # noqa
# src/tgvH.m:14524:V_CALCULMAJO : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias CALCULMAJO : "pour appel Denature rappel" ; # noqa
# src/tgvH.m:14588:V_ETCVL : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias ETCVL : "ISF : Variable relative a l etat civil " type BOOLEEN ; # noqa
# src/tgvH.m:14604:V_IND_TRAIT : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias IND_TRAIT : "indicateur de nature de trait. primitif ou correctif" ; # noqa
# src/tgvH.m:14682:V_REGANT : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias REGANT : "Valeur de REGCO evt -1 calculee " ; # noqa
# src/tgvH.m:14683:V_REGCO : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias REGCO : "Valeur de REGCO calculee (Cf. VALREGCO)" ; # noqa
# src/tgvH.m:14826:V_ROLCSG : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias ROLCSG : "numero de role CSG" ; # noqa
# src/tgvH.m:14880:V_TRCT : saisie contexte classe = 0 priorite = 51 categorie_TL = 10 alias TRCT : "Variable relative a la telecorrection" ; # noqa
# src/tgvH.m:14339:V_0DA : saisie famille classe = 0 priorite = 20 categorie_TL = 10 nat_code = 0 restituee alias 0DA : "Annee de naissance du declarant" type DATE_AAAA ;
saisie_variables = {}
# src/tgvH.m:14604:V_IND_TRAIT : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias IND_TRAIT : "indicateur de nature de trait. primitif ou correctif" ; # noqa
# 0 = primitif ?
# 4 = calcul correctif mais utilisant d'autres calculs que le 5 ?
# 5 = calcul correctif
saisie_variables['V_IND_TRAIT'] = 0
# src/tgvH.m:14211:VALREGCO : saisie revenu classe = 0 priorite = 20 categorie_TL = 10 cotsoc = 5 ind_abat = 0 acompte = 1 avfisc = 0 rapcat = 13 sanction = 0 nat_code = 1 alias 8ZA : "Regime d'imposition des non residents - Valeur 1, 2 ou 4" ; # noqa
saisie_variables['VALREGCO'] = 1
saisie_variables = dict(iter_saisie_variables(args.saisie_variables)) \
if args.saisie_variables is not None \
else {}
# saisie_variables['V_IND_TRAIT'] = 0
# src/tgvH.m:14404:V_ANREV : saisie contexte classe = 0 priorite = 10 categorie_TL = 20 alias ANREV : "Annee des revenus" type REEL ; # noqa
saisie_variables['V_ANREV'] = 2014
# src/tgvH.m:14339:V_0DA : saisie famille classe = 0 priorite = 20 categorie_TL = 10 nat_code = 0 restituee alias 0DA : "Annee de naissance du declarant" type DATE_AAAA ; # noqa
saisie_variables['V_0DA'] = 1980
# src/tgvH.m:14317:V_0AC : saisie famille classe = 0 priorite = 20 categorie_TL = 10 nat_code = 0 alias 0AC : "Case a cocher : situation de famille Celibataire" type BOOLEEN ; # noqa
saisie_variables['V_0AC'] = 1
if args.saisie_variables is not None:
saisie_variables.update(iter_saisie_variables(args.saisie_variables))
log.debug('saisie_variables: {}'.format(saisie_variables))
base_variables_value_by_name = {} # Override base variables values
......@@ -122,12 +134,41 @@ def main():
)
# log.debug('formulas_results: {}'.format(formulas_results))
requested_variables_calculees = list(iter_variables_calculees(args.calculees))
# FIXME Verify that variables_calculees has type "restituee".
print(keyfilter(
lambda key: key in requested_variables_calculees,
formulas_results,
))
try:
requested_formula_result = formulas_results[args.calculee_variable]()
except:
# import json
import traceback
exc_type, exc_value, exc_traceback = sys.exc_info()
frames = traceback.extract_tb(exc_traceback)
function_names = pipe(frames, map(attrgetter('name')), take_nth(2), drop(1), list)
print(function_names)
# name_and_dependencies_pairs = list(zip(
# function_names,
# map(
# lambda formula_name: list(map(
# lambda dependency_name: (
# dependency_name,
# formulas_helpers.result_by_formula_name_cache.get(dependency_name),
# ),
# variables_dependencies_by_name[formula_name],
# )),
# function_names,
# ),
# ))
# from pprint import pprint
# pprint(name_and_dependencies_pairs)
# function_values = map(
# lambda formula_name: formulas_helpers.result_by_formula_name_cache[formula_name],
# function_names,
# )
# function_name_and_value_pairs = list(zip(function_names, function_values))
# print(function_name_and_value_pairs)
raise
else:
print('{} = {}'.format(args.calculee_variable, requested_formula_result))
return 0
......
......@@ -187,44 +187,32 @@ def main():
ordered_formulas_names = read_json_file(json_file_name=os.path.join('data', 'ordered_formulas.json'))
# # Transpile initial formulas (used by verification functions)
#
# chap_ini_formula_source_by_name = dict(list(mapcat(
# load_regles_file,
# iter_ast_json_file_names(filenames=['chap-ini.json']),
# )))
# chap_ini_formulas_names = filter(
# lambda formula_name: formula_name in chap_ini_formula_source_by_name,
# ordered_formulas_names,
# )
# write_source_file(
# file_name='chap_ini_formulas.py',
# source=formulas_file_source(map(
# lambda formula_name: chap_ini_formula_source_by_name[formula_name],
# chap_ini_formulas_names,
# )),
# )
# Transpile formulas
formula_source_by_name = dict(list(mapcat(
formula_name_and_source_pairs = list(mapcat(
load_regles_file,
iter_ast_json_file_names(
# excluded_filenames=['chap-ini.json'],
filenames=['chap-*.json', 'res-ser*.json'],
),
)))
# formulas_names = filter(
# lambda formula_name: python_source_visitors.sanitized_variable_name(formula_name) in formula_source_by_name,
# ordered_formulas_names,
# )
iter_ast_json_file_names(filenames=['chap-*.json', 'res-ser*.json']),
))
source_by_formula_name = {}
for formula_name, source in formula_name_and_source_pairs:
if formula_name not in variable_definition_by_name:
log.warning('Formula "{}" has no definition.'.format(formula_name))
else:
applications = variable_definition_by_name[formula_name]['applications']
if formula_name not in source_by_formula_name or 'batch' in applications:
if formula_name in source_by_formula_name and 'batch' in applications:
log.warning('Formula "{}" already met from another application, '
'but this one of "batch" is prefered => keep the source of this one ({}).'.format(
formula_name, source))
source_by_formula_name[formula_name] = source
def get_formula_source(formula_name):
sanitized_formula_name = python_source_visitors.sanitized_variable_name(formula_name)
return formula_source_by_name[sanitized_formula_name] \
if sanitized_formula_name in formula_source_by_name \
else '# WARNING: the variable "{0}" is used in a formula at least, but is not defined.\n{0} = 0'.format(
sanitized_formula_name)
return source_by_formula_name[sanitized_formula_name] \
if sanitized_formula_name in source_by_formula_name \
else ('# WARNING: the variable "{name}" is used in a formula at least, but is not defined.\n' +
'def {name}(): return 0').format(name=sanitized_formula_name)
write_source_file(
file_name='formulas.py',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment