OpenFisca-Core-test 46.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openfisca_core/__init__.py +0 -0
- openfisca_core/commons/__init__.py +18 -0
- openfisca_core/commons/dummy.py +25 -0
- openfisca_core/commons/formulas.py +121 -0
- openfisca_core/commons/misc.py +109 -0
- openfisca_core/commons/py.typed +0 -0
- openfisca_core/commons/rates.py +114 -0
- openfisca_core/commons/tests/__init__.py +0 -0
- openfisca_core/commons/tests/test_dummy.py +9 -0
- openfisca_core/commons/tests/test_formulas.py +75 -0
- openfisca_core/commons/tests/test_rates.py +26 -0
- openfisca_core/data_storage/__init__.py +7 -0
- openfisca_core/data_storage/in_memory_storage.py +198 -0
- openfisca_core/data_storage/on_disk_storage.py +307 -0
- openfisca_core/data_storage/types.py +14 -0
- openfisca_core/entities/__init__.py +23 -0
- openfisca_core/entities/_core_entity.py +217 -0
- openfisca_core/entities/_description.py +55 -0
- openfisca_core/entities/entity.py +60 -0
- openfisca_core/entities/group_entity.py +123 -0
- openfisca_core/entities/helpers.py +165 -0
- openfisca_core/entities/py.typed +0 -0
- openfisca_core/entities/role.py +92 -0
- openfisca_core/entities/tests/__init__.py +0 -0
- openfisca_core/entities/tests/test_entity.py +10 -0
- openfisca_core/entities/tests/test_group_entity.py +70 -0
- openfisca_core/entities/tests/test_role.py +11 -0
- openfisca_core/entities/types.py +44 -0
- openfisca_core/errors/__init__.py +64 -0
- openfisca_core/errors/cycle_error.py +2 -0
- openfisca_core/errors/empty_argument_error.py +39 -0
- openfisca_core/errors/nan_creation_error.py +2 -0
- openfisca_core/errors/parameter_not_found_error.py +16 -0
- openfisca_core/errors/parameter_parsing_error.py +18 -0
- openfisca_core/errors/period_mismatch_error.py +9 -0
- openfisca_core/errors/situation_parsing_error.py +26 -0
- openfisca_core/errors/spiral_error.py +2 -0
- openfisca_core/errors/variable_name_config_error.py +2 -0
- openfisca_core/errors/variable_not_found_error.py +29 -0
- openfisca_core/experimental/__init__.py +9 -0
- openfisca_core/experimental/_errors.py +5 -0
- openfisca_core/experimental/_memory_config.py +41 -0
- openfisca_core/formula_helpers.py +9 -0
- openfisca_core/holders/__init__.py +33 -0
- openfisca_core/holders/helpers.py +93 -0
- openfisca_core/holders/holder.py +328 -0
- openfisca_core/holders/tests/__init__.py +0 -0
- openfisca_core/holders/tests/test_helpers.py +134 -0
- openfisca_core/holders/types.py +3 -0
- openfisca_core/indexed_enums/__init__.py +18 -0
- openfisca_core/indexed_enums/_enum_type.py +79 -0
- openfisca_core/indexed_enums/_errors.py +35 -0
- openfisca_core/indexed_enums/_guards.py +209 -0
- openfisca_core/indexed_enums/_utils.py +180 -0
- openfisca_core/indexed_enums/config.py +6 -0
- openfisca_core/indexed_enums/enum.py +218 -0
- openfisca_core/indexed_enums/enum_array.py +316 -0
- openfisca_core/indexed_enums/py.typed +0 -0
- openfisca_core/indexed_enums/tests/__init__.py +0 -0
- openfisca_core/indexed_enums/tests/test_enum.py +135 -0
- openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
- openfisca_core/indexed_enums/types.py +49 -0
- openfisca_core/memory_config.py +9 -0
- openfisca_core/model_api.py +71 -0
- openfisca_core/parameters/__init__.py +71 -0
- openfisca_core/parameters/at_instant_like.py +17 -0
- openfisca_core/parameters/config.py +55 -0
- openfisca_core/parameters/helpers.py +106 -0
- openfisca_core/parameters/parameter.py +217 -0
- openfisca_core/parameters/parameter_at_instant.py +75 -0
- openfisca_core/parameters/parameter_node.py +176 -0
- openfisca_core/parameters/parameter_node_at_instant.py +57 -0
- openfisca_core/parameters/parameter_scale.py +121 -0
- openfisca_core/parameters/parameter_scale_bracket.py +7 -0
- openfisca_core/parameters/values_history.py +5 -0
- openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
- openfisca_core/periods/__init__.py +77 -0
- openfisca_core/periods/_errors.py +28 -0
- openfisca_core/periods/_parsers.py +122 -0
- openfisca_core/periods/config.py +20 -0
- openfisca_core/periods/date_unit.py +110 -0
- openfisca_core/periods/helpers.py +314 -0
- openfisca_core/periods/instant_.py +226 -0
- openfisca_core/periods/period_.py +919 -0
- openfisca_core/periods/py.typed +0 -0
- openfisca_core/periods/tests/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
- openfisca_core/periods/tests/helpers/test_instant.py +73 -0
- openfisca_core/periods/tests/helpers/test_period.py +134 -0
- openfisca_core/periods/tests/test_instant.py +32 -0
- openfisca_core/periods/tests/test_parsers.py +129 -0
- openfisca_core/periods/tests/test_period.py +283 -0
- openfisca_core/populations/__init__.py +64 -0
- openfisca_core/populations/_core_population.py +454 -0
- openfisca_core/populations/_errors.py +65 -0
- openfisca_core/populations/group_population.py +326 -0
- openfisca_core/populations/population.py +142 -0
- openfisca_core/populations/tests/__init__.py +0 -0
- openfisca_core/populations/tests/test_members_position.py +116 -0
- openfisca_core/populations/types.py +110 -0
- openfisca_core/projectors/__init__.py +39 -0
- openfisca_core/projectors/entity_to_person_projector.py +12 -0
- openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
- openfisca_core/projectors/helpers.py +139 -0
- openfisca_core/projectors/projector.py +38 -0
- openfisca_core/projectors/typing.py +27 -0
- openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
- openfisca_core/rates.py +9 -0
- openfisca_core/reforms/__init__.py +24 -0
- openfisca_core/reforms/in_yaml_test_reform.py +28 -0
- openfisca_core/reforms/reform.py +86 -0
- openfisca_core/scripts/__init__.py +106 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/__init__.py +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/index.html +140 -0
- openfisca_core/scripts/find_placeholders.py +51 -0
- openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
- openfisca_core/scripts/measure_performances.py +291 -0
- openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
- openfisca_core/scripts/migrations/__init__.py +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
- openfisca_core/scripts/migrations/v24_to_25.py +156 -0
- openfisca_core/scripts/openfisca_command.py +192 -0
- openfisca_core/scripts/remove_fuzzy.py +213 -0
- openfisca_core/scripts/run_test.py +51 -0
- openfisca_core/scripts/simulation_generator.py +90 -0
- openfisca_core/simulation_builder.py +16 -0
- openfisca_core/simulations/__init__.py +45 -0
- openfisca_core/simulations/_build_default_simulation.py +158 -0
- openfisca_core/simulations/_build_from_variables.py +230 -0
- openfisca_core/simulations/_type_guards.py +298 -0
- openfisca_core/simulations/helpers.py +111 -0
- openfisca_core/simulations/simulation.py +618 -0
- openfisca_core/simulations/simulation_builder.py +855 -0
- openfisca_core/simulations/typing.py +203 -0
- openfisca_core/taxbenefitsystems/__init__.py +26 -0
- openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
- openfisca_core/taxscales/__init__.py +35 -0
- openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
- openfisca_core/taxscales/abstract_tax_scale.py +53 -0
- openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
- openfisca_core/taxscales/helpers.py +44 -0
- openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
- openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
- openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
- openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
- openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
- openfisca_core/taxscales/tax_scale_like.py +65 -0
- openfisca_core/tools/__init__.py +94 -0
- openfisca_core/tools/parallel_plugin.py +55 -0
- openfisca_core/tools/simulation_dumper.py +137 -0
- openfisca_core/tools/test_runner.py +802 -0
- openfisca_core/tracers/__init__.py +40 -0
- openfisca_core/tracers/computation_log.py +196 -0
- openfisca_core/tracers/flat_trace.py +87 -0
- openfisca_core/tracers/full_tracer.py +172 -0
- openfisca_core/tracers/performance_log.py +133 -0
- openfisca_core/tracers/simple_tracer.py +64 -0
- openfisca_core/tracers/trace_node.py +121 -0
- openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
- openfisca_core/types.py +548 -0
- openfisca_core/variables/__init__.py +26 -0
- openfisca_core/variables/config.py +53 -0
- openfisca_core/variables/helpers.py +67 -0
- openfisca_core/variables/tests/__init__.py +0 -0
- openfisca_core/variables/tests/test_definition_period.py +43 -0
- openfisca_core/variables/variable.py +474 -0
- openfisca_core/warnings/__init__.py +25 -0
- openfisca_core/warnings/libyaml_warning.py +2 -0
- openfisca_core/warnings/tempfile_warning.py +2 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
- openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
- openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
- openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
- openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
- openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
- openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
- openfisca_web_api/__init__.py +0 -0
- openfisca_web_api/app.py +226 -0
- openfisca_web_api/errors.py +11 -0
- openfisca_web_api/handlers.py +90 -0
- openfisca_web_api/loader/__init__.py +22 -0
- openfisca_web_api/loader/entities.py +28 -0
- openfisca_web_api/loader/parameters.py +122 -0
- openfisca_web_api/loader/spec.py +168 -0
- openfisca_web_api/loader/tax_benefit_system.py +27 -0
- openfisca_web_api/loader/variables.py +121 -0
- openfisca_web_api/openAPI.yml +434 -0
- openfisca_web_api/scripts/__init__.py +0 -0
- openfisca_web_api/scripts/serve.py +99 -0
openfisca_web_api/app.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
from openfisca_core.errors import PeriodMismatchError, SituationParsingError
|
|
6
|
+
from openfisca_web_api import handlers
|
|
7
|
+
from openfisca_web_api.errors import handle_import_error
|
|
8
|
+
from openfisca_web_api.loader import build_data
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import werkzeug.exceptions
|
|
12
|
+
from flask import Flask, abort, jsonify, make_response, redirect, request
|
|
13
|
+
from flask_cors import CORS
|
|
14
|
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
15
|
+
except ImportError as error:
|
|
16
|
+
handle_import_error(error)
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger("gunicorn.error")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def init_tracker(url, idsite, tracker_token):
|
|
22
|
+
try:
|
|
23
|
+
from openfisca_tracker.piwik import PiwikTracker
|
|
24
|
+
|
|
25
|
+
tracker = PiwikTracker(url, idsite, tracker_token)
|
|
26
|
+
|
|
27
|
+
info = os.linesep.join(
|
|
28
|
+
[
|
|
29
|
+
"You chose to activate the `tracker` module. ",
|
|
30
|
+
"Tracking data will be sent to: " + url,
|
|
31
|
+
"For more information, see <https://github.com/openfisca/openfisca-core#tracker-configuration>.",
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
log.info(info)
|
|
35
|
+
return tracker
|
|
36
|
+
|
|
37
|
+
except ImportError:
|
|
38
|
+
message = os.linesep.join(
|
|
39
|
+
[
|
|
40
|
+
traceback.format_exc(),
|
|
41
|
+
"You chose to activate the `tracker` module, but it is not installed.",
|
|
42
|
+
"For more information, see <https://github.com/openfisca/openfisca-core#tracker-installation>.",
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
log.warning(message)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_app(
|
|
49
|
+
tax_benefit_system,
|
|
50
|
+
tracker_url=None,
|
|
51
|
+
tracker_idsite=None,
|
|
52
|
+
tracker_token=None,
|
|
53
|
+
welcome_message=None,
|
|
54
|
+
):
|
|
55
|
+
if not tracker_url or not tracker_idsite:
|
|
56
|
+
tracker = None
|
|
57
|
+
else:
|
|
58
|
+
tracker = init_tracker(tracker_url, tracker_idsite, tracker_token)
|
|
59
|
+
|
|
60
|
+
app = Flask(__name__)
|
|
61
|
+
# Fix request.remote_addr to get the real client IP address
|
|
62
|
+
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
|
|
63
|
+
CORS(app, origins="*")
|
|
64
|
+
|
|
65
|
+
app.config["JSON_AS_ASCII"] = False # When False, lets jsonify encode to utf-8
|
|
66
|
+
app.url_map.strict_slashes = False # Accept url like /parameters/
|
|
67
|
+
app.url_map.merge_slashes = False # Do not eliminate // in paths
|
|
68
|
+
app.config["JSON_SORT_KEYS"] = False # Don't sort JSON keys in the Web API
|
|
69
|
+
|
|
70
|
+
data = build_data(tax_benefit_system)
|
|
71
|
+
|
|
72
|
+
DEFAULT_WELCOME_MESSAGE = "This is the root of an OpenFisca Web API. To learn how to use it, check the general documentation (https://openfisca.org/doc/) and the OpenAPI specification of this instance ({}spec)."
|
|
73
|
+
|
|
74
|
+
@app.before_request
|
|
75
|
+
def before_request():
|
|
76
|
+
if request.path != "/" and request.path.endswith("/"):
|
|
77
|
+
return redirect(request.path[:-1])
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
@app.route("/")
|
|
81
|
+
def get_root():
|
|
82
|
+
return (
|
|
83
|
+
jsonify(
|
|
84
|
+
{
|
|
85
|
+
"welcome": welcome_message
|
|
86
|
+
or DEFAULT_WELCOME_MESSAGE.format(request.host_url),
|
|
87
|
+
},
|
|
88
|
+
),
|
|
89
|
+
300,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@app.route("/parameters")
|
|
93
|
+
def get_parameters():
|
|
94
|
+
parameters = {
|
|
95
|
+
parameter["id"]: {
|
|
96
|
+
"description": parameter["description"],
|
|
97
|
+
"href": f"{request.host_url}parameter/{name}",
|
|
98
|
+
}
|
|
99
|
+
for name, parameter in data["parameters"].items()
|
|
100
|
+
if parameter.get("subparams")
|
|
101
|
+
is None # For now and for backward compat, don't show nodes in overview
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return jsonify(parameters)
|
|
105
|
+
|
|
106
|
+
@app.route("/parameter/<path:parameter_id>")
|
|
107
|
+
def get_parameter(parameter_id):
|
|
108
|
+
parameter = data["parameters"].get(parameter_id)
|
|
109
|
+
if parameter is None:
|
|
110
|
+
# Try legacy route
|
|
111
|
+
parameter_new_id = parameter_id.replace(".", "/")
|
|
112
|
+
parameter = data["parameters"].get(parameter_new_id)
|
|
113
|
+
if parameter is None:
|
|
114
|
+
raise abort(404)
|
|
115
|
+
return jsonify(parameter)
|
|
116
|
+
|
|
117
|
+
@app.route("/variables")
|
|
118
|
+
def get_variables():
|
|
119
|
+
variables = {
|
|
120
|
+
name: {
|
|
121
|
+
"description": variable["description"],
|
|
122
|
+
"href": f"{request.host_url}variable/{name}",
|
|
123
|
+
}
|
|
124
|
+
for name, variable in data["variables"].items()
|
|
125
|
+
}
|
|
126
|
+
return jsonify(variables)
|
|
127
|
+
|
|
128
|
+
@app.route("/variable/<id>")
|
|
129
|
+
def get_variable(id):
|
|
130
|
+
variable = data["variables"].get(id)
|
|
131
|
+
if variable is None:
|
|
132
|
+
raise abort(404)
|
|
133
|
+
return jsonify(variable)
|
|
134
|
+
|
|
135
|
+
@app.route("/entities")
|
|
136
|
+
def get_entities():
|
|
137
|
+
return jsonify(data["entities"])
|
|
138
|
+
|
|
139
|
+
@app.route("/spec")
|
|
140
|
+
def get_spec():
|
|
141
|
+
scheme = request.environ["wsgi.url_scheme"]
|
|
142
|
+
host = request.host
|
|
143
|
+
url = f"{scheme}://{host}"
|
|
144
|
+
|
|
145
|
+
return jsonify(
|
|
146
|
+
{
|
|
147
|
+
**data["openAPI_spec"],
|
|
148
|
+
"servers": [{"url": url}],
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def handle_invalid_json(error) -> None:
|
|
153
|
+
json_response = jsonify(
|
|
154
|
+
{
|
|
155
|
+
"error": f"Invalid JSON: {error.args[0]}",
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
abort(make_response(json_response, 400))
|
|
160
|
+
|
|
161
|
+
@app.route("/calculate", methods=["POST"])
|
|
162
|
+
def calculate():
|
|
163
|
+
tax_benefit_system = data["tax_benefit_system"]
|
|
164
|
+
request.on_json_loading_failed = handle_invalid_json
|
|
165
|
+
input_data = request.get_json()
|
|
166
|
+
try:
|
|
167
|
+
result = handlers.calculate(tax_benefit_system, input_data)
|
|
168
|
+
except (SituationParsingError, PeriodMismatchError) as e:
|
|
169
|
+
abort(make_response(jsonify(e.error), e.code or 400))
|
|
170
|
+
except (UnicodeEncodeError, UnicodeDecodeError) as e:
|
|
171
|
+
abort(
|
|
172
|
+
make_response(
|
|
173
|
+
jsonify({"error": "'" + e[1] + "' is not a valid ASCII value."}),
|
|
174
|
+
400,
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
return jsonify(result)
|
|
178
|
+
|
|
179
|
+
@app.route("/trace", methods=["POST"])
|
|
180
|
+
def trace():
|
|
181
|
+
tax_benefit_system = data["tax_benefit_system"]
|
|
182
|
+
request.on_json_loading_failed = handle_invalid_json
|
|
183
|
+
input_data = request.get_json()
|
|
184
|
+
try:
|
|
185
|
+
result = handlers.trace(tax_benefit_system, input_data)
|
|
186
|
+
except (SituationParsingError, PeriodMismatchError) as e:
|
|
187
|
+
abort(make_response(jsonify(e.error), e.code or 400))
|
|
188
|
+
return jsonify(result)
|
|
189
|
+
|
|
190
|
+
@app.after_request
|
|
191
|
+
def apply_headers(response):
|
|
192
|
+
response.headers.extend(
|
|
193
|
+
{
|
|
194
|
+
"Country-Package": data["country_package_metadata"]["name"],
|
|
195
|
+
"Country-Package-Version": data["country_package_metadata"]["version"],
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
return response
|
|
199
|
+
|
|
200
|
+
@app.after_request
|
|
201
|
+
def track_requests(response):
|
|
202
|
+
if tracker:
|
|
203
|
+
if request.headers.get("dnt"):
|
|
204
|
+
source_ip = ""
|
|
205
|
+
elif request.headers.get("X-Forwarded-For"):
|
|
206
|
+
source_ip = request.headers["X-Forwarded-For"].split(", ")[0]
|
|
207
|
+
else:
|
|
208
|
+
source_ip = request.remote_addr
|
|
209
|
+
|
|
210
|
+
api_version = "{}-{}".format(
|
|
211
|
+
data["country_package_metadata"]["name"],
|
|
212
|
+
data["country_package_metadata"]["version"],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
tracker.track(request.url, source_ip, api_version, request.path)
|
|
216
|
+
return response
|
|
217
|
+
|
|
218
|
+
@app.errorhandler(500)
|
|
219
|
+
def internal_server_error(e: werkzeug.exceptions.InternalServerError):
|
|
220
|
+
message = getattr(e, "original_exception", e.description)
|
|
221
|
+
response = jsonify({"error": "Internal server error: " + str(message)})
|
|
222
|
+
response.status_code = 500
|
|
223
|
+
|
|
224
|
+
return response
|
|
225
|
+
|
|
226
|
+
return app
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import NoReturn
|
|
3
|
+
|
|
4
|
+
log = logging.getLogger("gunicorn.error")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def handle_import_error(error) -> NoReturn:
|
|
8
|
+
msg = f"OpenFisca is missing some dependencies to run the Web API: '{error}'. To install them, run `pip install openfisca_core[web-api]`."
|
|
9
|
+
raise ImportError(
|
|
10
|
+
msg,
|
|
11
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import dpath
|
|
2
|
+
|
|
3
|
+
from openfisca_core.indexed_enums import Enum
|
|
4
|
+
from openfisca_core.simulations import SimulationBuilder
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def calculate(tax_benefit_system, input_data: dict) -> dict:
|
|
8
|
+
"""Returns the input_data where the None values are replaced by the calculated values."""
|
|
9
|
+
simulation = SimulationBuilder().build_from_entities(tax_benefit_system, input_data)
|
|
10
|
+
requested_computations = dpath.search(
|
|
11
|
+
input_data,
|
|
12
|
+
"*/*/*/*",
|
|
13
|
+
afilter=lambda t: t is None,
|
|
14
|
+
yielded=True,
|
|
15
|
+
)
|
|
16
|
+
computation_results: dict = {}
|
|
17
|
+
for computation in requested_computations:
|
|
18
|
+
path = computation[
|
|
19
|
+
0
|
|
20
|
+
] # format: entity_plural/entity_instance_id/openfisca_variable_name/period
|
|
21
|
+
entity_plural, entity_id, variable_name, period = path.split("/")
|
|
22
|
+
variable = tax_benefit_system.get_variable(variable_name)
|
|
23
|
+
result = simulation.calculate(variable_name, period)
|
|
24
|
+
population = simulation.get_population(entity_plural)
|
|
25
|
+
entity_index = population.get_index(entity_id)
|
|
26
|
+
|
|
27
|
+
if variable.value_type == Enum:
|
|
28
|
+
entity_result = result.decode()[entity_index].name
|
|
29
|
+
elif variable.value_type == float:
|
|
30
|
+
entity_result = float(
|
|
31
|
+
str(result[entity_index]),
|
|
32
|
+
) # To turn the float32 into a regular float without adding confusing extra decimals. There must be a better way.
|
|
33
|
+
elif variable.value_type == str:
|
|
34
|
+
entity_result = str(result[entity_index])
|
|
35
|
+
else:
|
|
36
|
+
entity_result = result.tolist()[entity_index]
|
|
37
|
+
# Don't use dpath.new, because there is a problem with dpath>=2.0
|
|
38
|
+
# when we have a key that is numeric, like the year.
|
|
39
|
+
# See https://github.com/dpath-maintainers/dpath-python/issues/160
|
|
40
|
+
if computation_results == {}:
|
|
41
|
+
computation_results = {
|
|
42
|
+
entity_plural: {entity_id: {variable_name: {period: entity_result}}},
|
|
43
|
+
}
|
|
44
|
+
elif entity_plural in computation_results:
|
|
45
|
+
if entity_id in computation_results[entity_plural]:
|
|
46
|
+
if variable_name in computation_results[entity_plural][entity_id]:
|
|
47
|
+
computation_results[entity_plural][entity_id][variable_name][
|
|
48
|
+
period
|
|
49
|
+
] = entity_result
|
|
50
|
+
else:
|
|
51
|
+
computation_results[entity_plural][entity_id][variable_name] = {
|
|
52
|
+
period: entity_result,
|
|
53
|
+
}
|
|
54
|
+
else:
|
|
55
|
+
computation_results[entity_plural][entity_id] = {
|
|
56
|
+
variable_name: {period: entity_result},
|
|
57
|
+
}
|
|
58
|
+
else:
|
|
59
|
+
computation_results[entity_plural] = {
|
|
60
|
+
entity_id: {variable_name: {period: entity_result}},
|
|
61
|
+
}
|
|
62
|
+
dpath.merge(input_data, computation_results)
|
|
63
|
+
|
|
64
|
+
return input_data
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def trace(tax_benefit_system, input_data):
|
|
68
|
+
simulation = SimulationBuilder().build_from_entities(tax_benefit_system, input_data)
|
|
69
|
+
simulation.trace = True
|
|
70
|
+
|
|
71
|
+
requested_calculations = []
|
|
72
|
+
requested_computations = dpath.search(
|
|
73
|
+
input_data,
|
|
74
|
+
"*/*/*/*",
|
|
75
|
+
afilter=lambda t: t is None,
|
|
76
|
+
yielded=True,
|
|
77
|
+
)
|
|
78
|
+
for computation in requested_computations:
|
|
79
|
+
path = computation[0]
|
|
80
|
+
_entity_plural, _entity_id, variable_name, period = path.split("/")
|
|
81
|
+
requested_calculations.append(f"{variable_name}<{period!s}>")
|
|
82
|
+
simulation.calculate(variable_name, period)
|
|
83
|
+
|
|
84
|
+
trace = simulation.tracer.get_serialized_flat_trace()
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"trace": trace,
|
|
88
|
+
"entitiesDescription": simulation.describe_entities(),
|
|
89
|
+
"requestedCalculations": requested_calculations,
|
|
90
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from openfisca_web_api.loader.entities import build_entities
|
|
2
|
+
from openfisca_web_api.loader.parameters import build_parameters
|
|
3
|
+
from openfisca_web_api.loader.spec import build_openAPI_specification
|
|
4
|
+
from openfisca_web_api.loader.variables import build_variables
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_data(tax_benefit_system):
|
|
8
|
+
country_package_metadata = tax_benefit_system.get_package_metadata()
|
|
9
|
+
parameters = build_parameters(tax_benefit_system, country_package_metadata)
|
|
10
|
+
variables = build_variables(tax_benefit_system, country_package_metadata)
|
|
11
|
+
entities = build_entities(tax_benefit_system)
|
|
12
|
+
data = {
|
|
13
|
+
"tax_benefit_system": tax_benefit_system,
|
|
14
|
+
"country_package_metadata": country_package_metadata,
|
|
15
|
+
"openAPI_spec": None,
|
|
16
|
+
"parameters": parameters,
|
|
17
|
+
"variables": variables,
|
|
18
|
+
"entities": entities,
|
|
19
|
+
}
|
|
20
|
+
data["openAPI_spec"] = build_openAPI_specification(data)
|
|
21
|
+
|
|
22
|
+
return data
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
def build_entities(tax_benefit_system):
|
|
2
|
+
return {entity.key: build_entity(entity) for entity in tax_benefit_system.entities}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_entity(entity):
|
|
6
|
+
formatted_doc = entity.doc.strip()
|
|
7
|
+
|
|
8
|
+
formatted_entity = {
|
|
9
|
+
"plural": entity.plural,
|
|
10
|
+
"description": entity.label,
|
|
11
|
+
"documentation": formatted_doc,
|
|
12
|
+
}
|
|
13
|
+
if not entity.is_person:
|
|
14
|
+
formatted_entity["roles"] = {
|
|
15
|
+
role.key: build_role(role) for role in entity.roles
|
|
16
|
+
}
|
|
17
|
+
return formatted_entity
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_role(role):
|
|
21
|
+
formatted_role = {"plural": role.plural, "description": role.doc}
|
|
22
|
+
|
|
23
|
+
if role.max:
|
|
24
|
+
formatted_role["max"] = role.max
|
|
25
|
+
if role.subroles:
|
|
26
|
+
formatted_role["max"] = len(role.subroles)
|
|
27
|
+
|
|
28
|
+
return formatted_role
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import operator
|
|
3
|
+
|
|
4
|
+
from openfisca_core.parameters import Parameter, ParameterNode, Scale
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_api_values_history(values_history):
|
|
8
|
+
api_values_history = {}
|
|
9
|
+
for value_at_instant in values_history.values_list:
|
|
10
|
+
api_values_history[value_at_instant.instant_str] = value_at_instant.value
|
|
11
|
+
|
|
12
|
+
return api_values_history
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_value(date, values):
|
|
16
|
+
candidates = sorted(
|
|
17
|
+
[
|
|
18
|
+
(start_date, value)
|
|
19
|
+
for start_date, value in values.items()
|
|
20
|
+
if start_date
|
|
21
|
+
<= date # dates are lexicographically ordered and can be sorted
|
|
22
|
+
],
|
|
23
|
+
reverse=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if candidates:
|
|
27
|
+
return candidates[0][1]
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_api_scale(scale, value_key_name):
|
|
32
|
+
# preprocess brackets for a scale with 'rates' or 'amounts'
|
|
33
|
+
brackets = [
|
|
34
|
+
{
|
|
35
|
+
"thresholds": build_api_values_history(bracket.threshold),
|
|
36
|
+
"values": build_api_values_history(getattr(bracket, value_key_name)),
|
|
37
|
+
}
|
|
38
|
+
for bracket in scale.brackets
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
dates = set(
|
|
42
|
+
functools.reduce(
|
|
43
|
+
operator.iadd,
|
|
44
|
+
[
|
|
45
|
+
list(bracket["thresholds"].keys()) + list(bracket["values"].keys())
|
|
46
|
+
for bracket in brackets
|
|
47
|
+
],
|
|
48
|
+
[],
|
|
49
|
+
),
|
|
50
|
+
) # flatten the dates and remove duplicates
|
|
51
|
+
|
|
52
|
+
# We iterate on all dates as we need to build the whole scale for each of them
|
|
53
|
+
api_scale = {}
|
|
54
|
+
for date in dates:
|
|
55
|
+
for bracket in brackets:
|
|
56
|
+
threshold_value = get_value(date, bracket["thresholds"])
|
|
57
|
+
if threshold_value is not None:
|
|
58
|
+
rate_or_amount_value = get_value(date, bracket["values"])
|
|
59
|
+
api_scale[date] = api_scale.get(date) or {}
|
|
60
|
+
api_scale[date][threshold_value] = rate_or_amount_value
|
|
61
|
+
|
|
62
|
+
# Handle stopped parameters: a parameter is stopped if its first bracket is stopped
|
|
63
|
+
latest_date_first_threshold = max(brackets[0]["thresholds"].keys())
|
|
64
|
+
latest_value_first_threshold = brackets[0]["thresholds"][
|
|
65
|
+
latest_date_first_threshold
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
if latest_value_first_threshold is None:
|
|
69
|
+
api_scale[latest_date_first_threshold] = None
|
|
70
|
+
|
|
71
|
+
return api_scale
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_source_url(absolute_file_path, country_package_metadata):
|
|
75
|
+
relative_path = absolute_file_path.replace(country_package_metadata["location"], "")
|
|
76
|
+
return "{}/blob/{}{}".format(
|
|
77
|
+
country_package_metadata["repository_url"],
|
|
78
|
+
country_package_metadata["version"],
|
|
79
|
+
relative_path,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_api_parameter(parameter, country_package_metadata):
|
|
84
|
+
api_parameter = {
|
|
85
|
+
"description": getattr(parameter, "description", None),
|
|
86
|
+
"id": parameter.name,
|
|
87
|
+
"metadata": parameter.metadata,
|
|
88
|
+
}
|
|
89
|
+
if parameter.file_path:
|
|
90
|
+
api_parameter["source"] = build_source_url(
|
|
91
|
+
parameter.file_path,
|
|
92
|
+
country_package_metadata,
|
|
93
|
+
)
|
|
94
|
+
if isinstance(parameter, Parameter):
|
|
95
|
+
if parameter.documentation:
|
|
96
|
+
api_parameter["documentation"] = parameter.documentation.strip()
|
|
97
|
+
api_parameter["values"] = build_api_values_history(parameter)
|
|
98
|
+
elif isinstance(parameter, Scale):
|
|
99
|
+
if "rate" in parameter.brackets[0].children:
|
|
100
|
+
api_parameter["brackets"] = build_api_scale(parameter, "rate")
|
|
101
|
+
elif "amount" in parameter.brackets[0].children:
|
|
102
|
+
api_parameter["brackets"] = build_api_scale(parameter, "amount")
|
|
103
|
+
elif isinstance(parameter, ParameterNode):
|
|
104
|
+
if parameter.documentation:
|
|
105
|
+
api_parameter["documentation"] = parameter.documentation.strip()
|
|
106
|
+
api_parameter["subparams"] = {
|
|
107
|
+
child_name: {
|
|
108
|
+
"description": child.description,
|
|
109
|
+
}
|
|
110
|
+
for child_name, child in parameter.children.items()
|
|
111
|
+
}
|
|
112
|
+
return api_parameter
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_parameters(tax_benefit_system, country_package_metadata):
|
|
116
|
+
return {
|
|
117
|
+
parameter.name.replace(".", "/"): build_api_parameter(
|
|
118
|
+
parameter,
|
|
119
|
+
country_package_metadata,
|
|
120
|
+
)
|
|
121
|
+
for parameter in tax_benefit_system.parameters.get_descendants()
|
|
122
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
import dpath
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from openfisca_core.indexed_enums import Enum
|
|
8
|
+
from openfisca_web_api import handlers
|
|
9
|
+
|
|
10
|
+
OPEN_API_CONFIG_FILE = os.path.join(
|
|
11
|
+
os.path.dirname(os.path.abspath(__file__)),
|
|
12
|
+
os.path.pardir,
|
|
13
|
+
"openAPI.yml",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_openAPI_specification(api_data):
|
|
18
|
+
tax_benefit_system = api_data["tax_benefit_system"]
|
|
19
|
+
with open(OPEN_API_CONFIG_FILE) as file:
|
|
20
|
+
spec = yaml.safe_load(file)
|
|
21
|
+
country_package_name = api_data["country_package_metadata"]["name"].title()
|
|
22
|
+
country_package_version = api_data["country_package_metadata"]["version"]
|
|
23
|
+
dpath.new(
|
|
24
|
+
spec,
|
|
25
|
+
"info/title",
|
|
26
|
+
spec["info"]["title"].replace("{COUNTRY_PACKAGE_NAME}", country_package_name),
|
|
27
|
+
)
|
|
28
|
+
dpath.new(
|
|
29
|
+
spec,
|
|
30
|
+
"info/description",
|
|
31
|
+
spec["info"]["description"].replace(
|
|
32
|
+
"{COUNTRY_PACKAGE_NAME}",
|
|
33
|
+
country_package_name,
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
dpath.new(
|
|
37
|
+
spec,
|
|
38
|
+
"info/version",
|
|
39
|
+
spec["info"]["version"].replace(
|
|
40
|
+
"{COUNTRY_PACKAGE_VERSION}",
|
|
41
|
+
country_package_version,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
for entity in tax_benefit_system.entities:
|
|
46
|
+
name = entity.key.title()
|
|
47
|
+
spec["components"]["schemas"][name] = get_entity_json_schema(
|
|
48
|
+
entity,
|
|
49
|
+
tax_benefit_system,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
situation_schema = get_situation_json_schema(tax_benefit_system)
|
|
53
|
+
dpath.new(spec, "components/schemas/SituationInput", situation_schema)
|
|
54
|
+
dpath.new(spec, "components/schemas/SituationOutput", situation_schema.copy())
|
|
55
|
+
dpath.new(
|
|
56
|
+
spec,
|
|
57
|
+
"components/schemas/Trace/properties/entitiesDescription/properties",
|
|
58
|
+
{
|
|
59
|
+
entity.plural: {"type": "array", "items": {"type": "string"}}
|
|
60
|
+
for entity in tax_benefit_system.entities
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Get example from the served tax benefist system
|
|
65
|
+
|
|
66
|
+
if tax_benefit_system.open_api_config.get("parameter_example"):
|
|
67
|
+
parameter_id = tax_benefit_system.open_api_config["parameter_example"]
|
|
68
|
+
parameter_path = parameter_id.replace(".", "/")
|
|
69
|
+
parameter_example = api_data["parameters"][parameter_path]
|
|
70
|
+
else:
|
|
71
|
+
parameter_example = next(iter(api_data["parameters"].values()))
|
|
72
|
+
dpath.new(spec, "components/schemas/Parameter/example", parameter_example)
|
|
73
|
+
|
|
74
|
+
if tax_benefit_system.open_api_config.get("variable_example"):
|
|
75
|
+
variable_example = api_data["variables"][
|
|
76
|
+
tax_benefit_system.open_api_config["variable_example"]
|
|
77
|
+
]
|
|
78
|
+
else:
|
|
79
|
+
variable_example = next(iter(api_data["variables"].values()))
|
|
80
|
+
dpath.new(spec, "components/schemas/Variable/example", variable_example)
|
|
81
|
+
|
|
82
|
+
if tax_benefit_system.open_api_config.get("simulation_example"):
|
|
83
|
+
simulation_example = tax_benefit_system.open_api_config["simulation_example"]
|
|
84
|
+
dpath.new(
|
|
85
|
+
spec,
|
|
86
|
+
"components/schemas/SituationInput/example",
|
|
87
|
+
simulation_example,
|
|
88
|
+
)
|
|
89
|
+
dpath.new(
|
|
90
|
+
spec,
|
|
91
|
+
"components/schemas/SituationOutput/example",
|
|
92
|
+
handlers.calculate(tax_benefit_system, deepcopy(simulation_example)),
|
|
93
|
+
) # calculate has side-effects
|
|
94
|
+
dpath.new(
|
|
95
|
+
spec,
|
|
96
|
+
"components/schemas/Trace/example",
|
|
97
|
+
handlers.trace(tax_benefit_system, simulation_example),
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
message = f"No simulation example has been defined for this tax and benefit system. If you are the maintainer of {country_package_name}, you can define an example by following this documentation: https://openfisca.org/doc/openfisca-web-api/config-openapi.html"
|
|
101
|
+
dpath.new(spec, "components/schemas/SituationInput/example", message)
|
|
102
|
+
dpath.new(spec, "components/schemas/SituationOutput/example", message)
|
|
103
|
+
dpath.new(spec, "components/schemas/Trace/example", message)
|
|
104
|
+
return spec
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_variable_json_schema(variable):
|
|
108
|
+
result = {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"additionalProperties": {"type": variable.json_type},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if variable.value_type == Enum:
|
|
114
|
+
result["additionalProperties"]["enum"] = [
|
|
115
|
+
item.name for item in list(variable.possible_values)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_entity_json_schema(entity, tax_benefit_system):
|
|
122
|
+
if entity.is_person:
|
|
123
|
+
return {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"properties": {
|
|
126
|
+
variable_name: get_variable_json_schema(variable)
|
|
127
|
+
for variable_name, variable in tax_benefit_system.get_variables(
|
|
128
|
+
entity,
|
|
129
|
+
).items()
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": False,
|
|
132
|
+
}
|
|
133
|
+
properties = {}
|
|
134
|
+
properties.update(
|
|
135
|
+
{
|
|
136
|
+
role.plural or role.key: {"type": "array", "items": {"type": "string"}}
|
|
137
|
+
for role in entity.roles
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
properties.update(
|
|
141
|
+
{
|
|
142
|
+
variable_name: get_variable_json_schema(variable)
|
|
143
|
+
for variable_name, variable in tax_benefit_system.get_variables(
|
|
144
|
+
entity,
|
|
145
|
+
).items()
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
return {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"properties": properties,
|
|
151
|
+
"additionalProperties": False,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_situation_json_schema(tax_benefit_system):
|
|
156
|
+
return {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"additionalProperties": False,
|
|
159
|
+
"properties": {
|
|
160
|
+
entity.plural: {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": {
|
|
163
|
+
"$ref": f"#/components/schemas/{entity.key.title()}",
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
for entity in tax_benefit_system.entities
|
|
167
|
+
},
|
|
168
|
+
}
|