Commit 02b4b7dc authored by Lucas Détré's avatar Lucas Détré 😺
Browse files

Merge branch 'master' of ssh://git.beta.pole-emploi.fr:23/open-source/trefle into new-simulator

parents cb48562f 531eb5da
Pipeline #2876 passed with stage
in 1 minute and 28 seconds
__pycache__/
.mypy_cache/
.pytest_cache/
**/*/docs/
tmp/
*.egg-info
build/
......
......@@ -77,7 +77,6 @@ deploy Backoffice to staging:
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- ssh gitlab@${STAGING_SERVEUR_IP} "cd /home/docker && BACK_VERSION=${CI_COMMIT_TAG} docker-compose up -d --no-deps web && echo \"BACK_VERSION=${CI_COMMIT_TAG}\" | tee -a .env >> update.log"
only:
......
......@@ -9,7 +9,7 @@ server {
set $upstream_endpoint trefle;
location @gunicorn-master {
proxy_pass http://$upstream_endpoint:8000;
proxy_pass http://trefle:8000;
proxy_redirect off;
proxy_set_header Host $host;
}
......@@ -78,18 +78,4 @@ server {
location /legacy {
root /srv/trefle/legacy-backoffice;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
location /legacy/explorer {
alias /srv/trefle/legacy-backoffice/;
index index.html;
}
rewrite /legacy/(.*) /$1 break;
try_files $uri @gunicorn-master;
}
}
FROM ubuntu:xenial
LABEL maintainer="labonneformation@pole-emploi.fr"
ENV LANG C.UTF-8
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get -y install \
apt-utils\
software-properties-common\
unzip \
nginx \
vim \
bzip2 \
cron \
logrotate \
iptables \
git \
dnsutils
ARG TREFLE_GIT
ARG TREFLE_BACK_VERSION
RUN git clone -b ${TREFLE_BACK_VERSION} ${TREFLE_GIT} /srv/trefle && \
chown -R www-data: /srv/trefle && \
chmod -R u+rwx /srv/trefle
ARG TREFLE_GIT
ARG TREFLE_OLD_VERSION
RUN git clone -b ${TREFLE_OLD_VERSION} ${TREFLE_GIT} /srv/trefle-old && \
chown -R www-data: /srv/trefle-old && \
chmod -R u+rwx /srv/trefle-old
CMD service nginx start; \
cron -f
......@@ -32,6 +32,24 @@ async def simulate(data, financements):
del data[key]
async def simulate_remuneration(data, remunerations):
# Prepare context
flatten(data)
context = Context(data.copy())
routine.extrapolate_context(context)
routine.preprocess(context)
for remuneration in remunerations:
copy = context.copy()
routine.check_remuneration(context, remuneration)
data.update(copy.cleaned_data)
# FIXME (limits of the single-store-all context object)
# Clean keys not meant to be exposed
for key in list(data.keys()):
if key.startswith("remuneration"):
del data[key]
def get_financements(tags=None):
financements = [config.Financement(f) for f in config.FINANCEMENTS]
for tag in tags or []:
......
......@@ -10,7 +10,13 @@ from roll.extensions import cors
import ujson as json
from . import VERSION, get_financements, get_remunerations, simulate
from . import (
VERSION,
get_financements,
get_remunerations,
simulate,
simulate_remuneration,
)
from . import routine
from .config import AUTHORIZED, FINANCEMENTS, COMMIT_AUTHORIZED, GLOSSARY, IDCC, NAF, CERTIFINFO, RAW_RULES, SCHEMA, GITLAB_TOKEN
from .context import Context
......@@ -81,8 +87,8 @@ async def simulate_(request, response):
body = {"financements": financements}
if request.query.bool("context", False):
body["context"] = {
k: v for k, v in context.items()
if k in SCHEMA and "label" in SCHEMA[k]}
k: v for k, v in context.items() if k in SCHEMA and "label" in SCHEMA[k]
}
if request.query.bool("scenario", False):
body["scenario"] = make_scenario(context, financements)
response.json = body
......@@ -93,24 +99,10 @@ async def simulate_(request, response):
# TODO : add pointer error for bad region number + test
@app.route("/remuneration", methods=["POST"])
async def remuneration_(request, response):
data = request.json
context = request.json
remunerations = get_remunerations(tags=request.query.list("tags", []))
try:
flatten(data)
context = Context(data.copy())
routine.extrapolate_context(context)
routine.preprocess(context)
for remuneration in remunerations:
copy = context.copy()
routine.check_remuneration(context, remuneration)
data.update(copy.cleaned_data)
# FIXME (limits of the single-store-all context object)
# Clean keys not meant to be exposed
for key in list(data.keys()):
if key.startswith("remuneration"):
del data[key]
await simulate_remuneration(context, remunerations)
except DataError as err:
error = {err.key: err.error}
log_simulate(context, errors=error)
......
......@@ -287,6 +287,9 @@ Scénario: Cas parent isolé
Quand je demande un calcul de financement
Quand je sélectionne le financement «Action collective financée par la Région»
Alors la rémunération applicable vaut 863.0
Et le montant de l'aide au transport vaut «98,79€ par mois (sous condition)»
Et le montant de l'aide à l'hébergement vaut «101,84€ par mois (sous condition)»
Scénario: Cas non salarié ayant travaillé
Soit un bénéficiaire et une formation
......@@ -325,7 +328,7 @@ Scénario: Cas travailleur handicapé
Et c'est un travailleur handicapé
Quand je demande un calcul de financement
Quand je sélectionne le financement «Action collective financée par la Région»
Alors la rémunération applicable vaut 910.02
Alors la rémunération applicable vaut 1001.02
Scénario: Cas travailleur handicapé rémunération > 910
Soit un bénéficiaire et une formation
......@@ -354,7 +357,7 @@ Scénario: Cas travailleur handicapé rémunération
Et c'est un travailleur handicapé
Quand je demande un calcul de financement
Quand je sélectionne le financement «Action collective financée par la Région»
Alors la rémunération applicable vaut 910.02
Alors la rémunération applicable vaut 1001.02
Scénario: Cas ESSFIMO
Soit un bénéficiaire et une formation
......
Fonctionnalité: Rémunérations et aides en Bourgogne-Franche-Comté
Scénario: Rému et aides moins de 18 ans
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et l'âge du bénéficiaire vaut 16
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 455.01
Et une aide à l'hébergement est éligible
Et une aide au transport est éligible
Scénario: Rému et aides plus de 18 ans
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et l'âge du bénéficiaire vaut 36
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 652.18
Et aucune aide à l'hébergement n'est éligible
Et une aide au transport est éligible
Scénario: Rému et aides parent isolée
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et l'âge du bénéficiaire vaut 36
Et c'est un parent isolé
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 863.0
Et une aide à l'hébergement est éligible
Et une aide au transport est éligible
Scénario: Rému et aides moins de 18 ans ayant travaillé 12 mois dont au moins 6 consécutifs
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et l'âge du bénéficiaire vaut 17
Et c'est un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 708.59
Et aucune aide à l'hébergement n'est éligible
Et une aide au transport est éligible
Scénario: Rému pour une pesonnes handicapée ayant déjà travaillé
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et c'est un travailleur handicapé
Et c'est un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 910.02
Et aucune aide à l'hébergement n'est éligible
Et une aide au transport est éligible
Scénario: Rému pour une pesonnes handicapée n'ayant pas travaillée
Soit un bénéficiaire et une formation
Et c'est un demandeur d'emploi
Et c'est un travailleur handicapé
Et les codes financeur de la formation valent [«Conseil régional»]
Et la région de la formation vaut «Bourgogne-Franche-Comté»
Quand je demande un calcul de rémunération
Alors la rémunération applicable vaut 1001.02
Et aucune aide à l'hébergement n'est éligible
Et une aide au transport est éligible
from behave import given, when, then, use_step_matcher
from behave.api.async_step import async_run_until_complete
from trefle import simulate, get_financements
from trefle import simulate, simulate_remuneration, get_financements, get_remunerations
from trefle.config import FINANCEMENTS
from trefle.helpers import revert_dict
from trefle.helpers import revert_dict, remove_namespace
from trefle.rules import LABELS, SCHEMA, Pointer
INTITULES_FINANCEMENTS = set(f["intitule"] for f in FINANCEMENTS)
......@@ -49,6 +49,20 @@ async def when_simulate(context):
context.passed = [f for f in financements if f['eligible']]
@when('je demande un calcul de rémunération')
@async_run_until_complete
async def when_remunerate(context):
remunerations = get_remunerations()
await simulate_remuneration(context.data, remunerations)
context.passed = [r for r in remunerations if r['remuneration']]
for result in context.passed:
if result['remuneration']:
context.result = result
break
else:
raise AssertionError(f'No result found')
@then(r"il y a (?P<expected>\d+) financements? proposés?")
def then_check_count(context, expected):
assert found == int(expected), f'Found {found}'
......@@ -86,10 +100,7 @@ def then_check_organisme(context, name):
@then(r"(?:le |la |l')(?P<label>.*) vaut (?P<value>.+)")
def then_check_output(context, label, value):
value = Pointer(value).get({})
if(LABELS[label].startswith("financement")):
key = LABELS[label][12:] # Remove "financement." namespace.
if(LABELS[label].startswith("remuneration")):
key = LABELS[label][13:] # Remove "remuneration." namespace.
key = remove_namespace(LABELS[label])
assert context.result[key] == value, (f'key = {key} '
f'label = {LABELS[label]}'
f'{context.result[key]} '
......@@ -111,6 +122,21 @@ def then_check_true_boolean_value(context, label):
f'{key} is True'
@then(r"une? (?P<label>.+) est éligible")
def then_check_true_eligibility(context, label):
key = LABELS[label]
assert context.result.get(remove_namespace(key)) \
and context.result[remove_namespace(key)] is True,\
f'{label} is True'
@then(r"aucune? (?P<label>.+) n'est éligible")
def then_check_false_eligibility(context, label):
key = LABELS[label]
assert context.result.get(remove_namespace(key)) is None,\
f'{label} is False'
@then("aucun financement n'est proposé")
def then_no_results(context):
assert not context.passed, f"Results found: {context.passed}"
......@@ -3,30 +3,96 @@ Si les codes financeur de la formation contiennent «Conseil régional»
Si l'allocation du bénéficiaire est définie
Alors la rémunération applicable est égale au montant de l'allocation du bénéficiaire
# Par défaut
Si l'âge du bénéficiaire est inférieur à 18
Et la rémunération applicable est inférieure à 455.01
Alors la rémunération applicable vaut 455.01
Si l'âge du bénéficiaire est supérieur ou égal à 18
Et la rémunération applicable est inférieure à 652.18
Alors la rémunération applicable vaut 652.18
#####################
# Aide au transport #
#####################
Si c'est un travailleur handicapé
Alors l'aide au transport est éligible
Et le texte de l'aide au transport vaut «L'indemnité de transport est limitée à un aller/retour sur la durée de la formation.»
Si ce n'est pas un travailleur handicapé
Alors l'aide au transport est éligible
Et le montant de l'aide au transport vaut «98,79€ par mois (sous condition)»
Et le texte de l'aide au transport vaut «Si la distance vers le lieu de votre formation est supérieure ou égale à 16km. Alors vous pouvez bénéficier d'une aide au transport.»
# Ligne 45 46
Si c'est un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Et ce n'est pas un parent isolé
Et ce n'est pas une mère de famille ayant au moins trois enfants
Et ce n'est pas une femme divorcée, veuve ou séparée judiciairement depuis moins de trois ans
Et ce n'est pas une femme seule et enceinte
Alors l'aide au transport est éligible
Et le texte de l'aide au transport vaut «L'indemnité de transport est limitée à un aller/retour sur la durée de la formation.»
####################
# Aide à l'hébergement #
####################
Si c'est un bénéficiaire qui a déjà travaillé six mois sur une période de douze mois
Ou c'est un bénéficiaire qui a déjà travaillé douze mois sur une période de vingt-quatre mois
Et la rémunération applicable est inférieure à 863.00
Alors la rémunération applicable vaut 863.00
# Règles spécifiques
Si c'est un parent isolé
Ou c'est un parent isolé
Ou c'est une mère de famille ayant au moins trois enfants
Ou c'est une femme divorcée, veuve ou séparée judiciairement depuis moins de trois ans
Et la rémunération applicable est inférieure à 863.00
Alors la rémunération applicable vaut 863.00
Ou c'est une femme seule et enceinte
Alors l'aide à l'hébergement est éligible
Et le montant de l'aide à l'hébergement vaut «101,84€ par mois (sous condition)»
Et le texte de l'aide à l'hébergement vaut «Si la distance vers le lieu de votre formation est supérieure à 250km. Alors vous pouvez bénéficier d'une aide à l'hébergement. Cette aide est non cumulable avec l'aide au transport.»
# Tableau de regles: ligne 24
Si l'âge du bénéficiaire est inférieur à 18
Et ce n'est pas un bénéficiaire qui a déjà travaillé six mois sur une période de douze mois
Et ce n'est pas un bénéficiaire qui a déjà travaillé douze mois sur une période de vingt-quatre mois
Et ce n'est pas un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Alors l'aide à l'hébergement est éligible
Et le montant de l'aide à l'hébergement vaut «37,20€ par mois (sous condition)»
Et le texte de l'aide à l'hébergement vaut «Si la distance vers le lieu de votre formation est inférieure ou égale à 15km. Alors vous pouvez bénéficier d'une aide à l'hébergement.»
################
# Rémunération #
################
Si ce n'est pas un bénéficiaire qui a déjà travaillé six mois sur une période de douze mois
Et ce n'est pas un bénéficiaire qui a déjà travaillé douze mois sur une période de vingt-quatre mois
Et ce n'est pas un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Si l'âge du bénéficiaire est inférieur à 18
Et la rémunération applicable est inférieure à 455.01
Alors la rémunération applicable vaut 455.01
Si l'âge du bénéficiaire est supérieur ou égal à 18
Et la rémunération applicable est inférieure à 652.18
Alors la rémunération applicable vaut 652.18
Si c'est un travailleur handicapé
Et la rémunération applicable est inférieure à 1001.02
Alors la rémunération applicable vaut 1001.02
# Tableau de regles: ligne 4 et 7
Si c'est un travailleur handicapé
Et la rémunération applicable est inférieure à 910.02
Si c'est un bénéficiaire qui a déjà travaillé six mois sur une période de douze mois
Ou c'est un bénéficiaire qui a déjà travaillé douze mois sur une période de vingt-quatre mois
Ou c'est un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Alors la rémunération applicable vaut 910.02
Et le texte de la rémunération vaut «C'est une rémunération minimum en fonction de votre expérience»
# Tableau de regles: ligne 4 et 8 à 13
Si c'est un bénéficiaire qui a déjà travaillé six mois sur une période de douze mois
Ou c'est un bénéficiaire qui a déjà travaillé douze mois sur une période de vingt-quatre mois
Si la rémunération applicable est inférieure à 863.0
Alors la rémunération applicable vaut 863.00
# Tableau de regles: ligne 38 à 44
Si c'est un bénéficiaire non salarié qui a travaillé pendant douze mois dont au moins six mois consécutifs dans les trois ans qui précèdent l'entrée en formation
Et la rémunération applicable est inférieure à 708.59
Alors la rémunération applicable vaut 708.59
Si c'est un parent isolé
Ou c'est une mère de famille ayant au moins trois enfants
Ou c'est une femme divorcée, veuve ou séparée judiciairement depuis moins de trois ans
Ou c'est une femme seule et enceinte
Si la rémunération applicable est inférieure à 863.00
Alors la rémunération applicable vaut 863.00
# TODO ajouter cas détenu
# Si c'est un détenu
# Alors la rémunération applicable vaut 3.3 € x durée de la formation en h
......@@ -285,6 +285,11 @@ beneficiaire:
type: boolean
public: true
label: femme divorcée, veuve ou séparée judiciairement depuis moins de trois ans
seule_et_enceinte:
description: vous êtes une femme isolée et enceinte
type: boolean
public: true
label: femme seule et enceinte
creation_entreprise:
description: le bénéficiaire est en situation de création d'entreprise
alias:
......@@ -1043,6 +1048,53 @@ formation:
individuels:
type: boolean
label: formation ouverte aux bénéficiaires individuels
aide: &aide
droit_aide_complementaire:
description: le bénéficiaire a droit a une aide complémentaire
type: boolean
public: true
label: aide complémentaire
montant_aide_complementaire:
description: le montant de l'aide complémentaire
type: number
format: float
public: true
label: montant de l'aide complémentaire
description_aide_complementaire:
description: texte de description de l'aide complémentaire
type: string
public: true
label: texte de l'aide complémentaire
droit_aide_transport:
description: le bénéficiaire a droit à une aide au transport
type: boolean
public: true
label: aide au transport
montant_aide_transport:
description: le montant de l'aide au transport
type: string
public: true
label: montant de l'aide au transport
description_aide_transport:
description: texte de description de l'aide au transport
type: string
public: true
label: texte de l'aide au transport
droit_aide_hebergement:
description: le bénéficiaire a droit a une aide à l'hébergement
type: boolean
public: true
label: aide à l'hébergement
montant_aide_hebergement:
description: le montant de l'aide à l'hébergement
type: string
public: true
label: montant de l'aide à l'hébergement
description_aide_hebergement:
description: texte de description de l'aide au transport
type: string
public: true
label: texte de l'aide à l'hébergement
remuneration: &remuneration
intitule_remuneration: # add _remuneration to not overlap financement.intitule
description: le nom de la règle de rémuneration
......@@ -1118,6 +1170,7 @@ remuneration: &remuneration
nullable: true
public: true
label: date de fin de la rémunération applicable
<<: *aide
financement:
intitule:
description: le nom du financement
......
......@@ -140,6 +140,10 @@ def revert_dict(d):
return {v: k for k, v in d.items()}
def remove_namespace(s):
return s[(s.find('.') + 1):]
def json_path(pattern, data):
steps = pattern.split(".")
......
......@@ -54,3 +54,4 @@ add_schema("beneficiaire")
add_schema("formation")
add_schema("financement")
add_schema("remuneration")
add_schema("aide")
......@@ -254,6 +254,15 @@ def compute_remuneration(context, facility, facility_name="financement"):
"remuneration_annee_2",
"remuneration_annee_3",
"en_savoir_plus",
"droit_aide_complementaire",
"montant_aide_complementaire",
"description_aide_complementaire",
"droit_aide_transport",
"montant_aide_transport",
"description_aide_transport",
"droit_aide_hebergement",
"montant_aide_hebergement",
"description_aide_hebergement",
]
for key in keys:
name = facility_name + f".{key}"
......@@ -315,6 +324,8 @@ def check_remuneration(context, remuneration):
context["remuneration.intitule_remuneration"] = remuneration.intitule
context["remuneration.tags"] = remuneration.tags
context["financement.remuneration"] = 0 # not nullable for remuneration
if(not context.get("financement.intitule")):
context["financement.intitule"] = "none" # no financement targeted by default
rule_name = get_root_rule(context, remuneration)
if not rule_name:
return
......@@ -328,6 +339,9 @@ def check_remuneration(context, remuneration):
if key.startswith("remuneration"):
if(context.get("financement." + key[13:])):
context[key] = context.get("financement." + key[13:])
if key.startswith("aide"):
if(context.get("financement." + key[5:])):
context[key] = context.get("financement." + key[5:])
compute_remuneration(context, remuneration, facility_name="remuneration")
# load_organisme_contact_details(context, remuneration)
......
......@@ -15,17 +15,18 @@ def set_percent(context, key: Label, rate: float, value: Pointer):
context[key] = round(value.get(context) * rate / 100, 2)
@action(r"c'est une? (?P<key>.+)")
@action(r"(l'|les? |la )(?P<key>.+?) est éligible$")
def set_true(context, key: Label):
context[key] = True
@action(r"(l'|les? |la )(?P<key>.+?) (vaut|est) (l'|les? |la )?(?P<value>.+)$")
@action(r"(l'|les? |la )(?P<key>.+?) est égale? (à la |à l'|à |aux? )?(?P<value>.+)$")
def set_value(context, key: Label, value: Pointer):
context[key] = value.get(context)
@action(r"c'est une? (?P<key>.+)")
def set_true(context, key: Label):
context[key] = True
@action(r"appliquer les règles (de )?(l'|le |la )?(?P<rule>.+)")
def include(context, rule: Pointer):
name = rule.get(context)
......
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