⚙️ Implémentation du mécanisme barème

pull/6/head
Mael Thomas 2017-04-06 18:34:00 +02:00
parent 370e2a61be
commit 54e06324a0
18 changed files with 243 additions and 48 deletions

View File

@ -10,7 +10,6 @@ https://github.com/atom/atom/blob/master/CONTRIBUTING.md#git-commit-messages
- 💚 `:green_heart:` when fixing the CI build
- ✅ `:white_check_mark:` when adding tests
- ⬆️ `:arrow_up:` when upgrading dependencies
- ⬇️ `:arrow_down:` when downgrading dependencies
Et ceux spécifiques au projet :

View File

@ -0,0 +1,47 @@
Version 'OpenFisca'
```yaml
- seuil: 0
taux: 0%
- seuil: 1
taux: 7.8%
- seuil: 8
taux: 0%
```
Le problème de cette syntaxe, c'est que ce ne sont pas des tranches : la tranche 1 ne contient pas assez d'informations pour être calculée !
Version plus explicite :
```yaml
- de: 0
à: 1
taux: 0%
- de: 1
à: 8
taux: 7.8%
- de: 8
taux: 0%
```
Ou, étant donné que les barèmes se suivent a priori toujours et commencent à zéro :
```yaml
- en-dessous de: 1
taux: 0%
- de: 1
à: 8
taux: 7.8%
- au-dessus de: 8
taux: 0%
```
Autre version possible :
```yaml
seuils:
0: 0%
1: 7.8%
8: 0%
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -7,3 +7,6 @@ URSSAF:
salarié:
nom: Le salarié
image: salarié.png
AGIRC:
image: agirc.png
lien: https://fr.wikipedia.org/wiki/Agirc_et_Arrco

View File

@ -24,10 +24,11 @@
qui apporte à l'employeur plus de souplesse dans un cadre législatif précis, comportant en particulier des contreparties financières.
formule:
somme: #TODO à l'avenir, exprimer une somme par requête de type : obligation applicable au CDD
- CDD . CIF CDD #cotisation
- CDD . majoration chômage CDD #cotisation
- CDD . prime de fin de contrat #indemnité
- CDD . compensation des congés payés #indemnité
# - CDD . CIF CDD #cotisation
# - CDD . majoration chômage CDD #cotisation
# - CDD . prime de fin de contrat #indemnité
# - CDD . compensation des congés payés #indemnité
- AGIRC simple
- Variable: assiette cotisations sociales
attache: Salariat

View File

@ -0,0 +1,42 @@
- Cotisation: AGIRC simple
attache: Salariat
attributs:
branche: retraite
type de retraite: complémentaire
destinataire: AGIRC
description: |
Cotisation de retraite complémentaire cadre. Complète le régime ARRCO
(Association Générale des Institutions de Retraite des Cadres)
références:
calcul des cotisations: http://www.agirc-arrco.fr/entreprises/gerer-les-salaries/calcul-des-cotisations/
garantie minimale de points: http://www.journaldunet.com/management/pratique/primes-et-avantages/5079/gmp-2016-la-garantie-minimale-de-points-calcul-et-montant.html
# concerne:
# - statut cadre = oui
formule:
barème:
assiette: salaire de base
multiplicateur des tranches: plafond sécurité sociale
tranches:
- en-dessous de: 1
taux: 0%
- de: 1
à: 8
taux: 7.8%
# 2016: 7.8%
# 2015: 7.8%
# 2014: 7.75%
# 2006: 7.7%
# 1999: 7.5%
# 1998: 6.875%
# 1997: 6.25%
# 1996: 5.625%
# 1995: 5%
# 1994: 3.63%
# 1993-07: 2.34%
- au-dessus de: 8
taux: 0%
notes: |
Il éxiste une tranche C, de 4 à 8 fois la base, sur laquelle la répartition des cotisations est décidée au sein de lentreprise jusquà 20 %. De 20 % à 20,30 %, la répartition est la suivante : 66,67 % à la charge du salarié et 33,33 % pour lemployeur.

View File

@ -12,7 +12,7 @@
formule:
barème:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
composantes:
- attributs:
dû par: employeur

View File

@ -14,7 +14,7 @@
formule:
barème:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
composantes:
- attributs:
dû par: employeur

View File

@ -11,7 +11,7 @@
Attention: les tranches du barème sont différentes pour les cadres et non-cadres, en valeur et en nombres.
marginalRateTaxScale:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
- variable: AGFF
tags:

View File

@ -13,7 +13,7 @@
Il éxiste une tranche C, de 4 à 8 fois la base, sur laquelle la répartition des cotisations est décidée au sein de lentreprise jusquà 20 %. De 20 % à 20,30 %, la répartition est la suivante : 66,67 % à la charge du salarié et 33,33 % pour lemployeur.
marginalRateTaxScale:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
- variable: AGIRC
tags:

View File

@ -9,7 +9,7 @@
destinataire: ARRCO
marginalRateTaxScale:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
- variable: ARRCO

View File

@ -10,7 +10,7 @@
formule:
barème:
assiette: assiette sécurité sociale
assiette: assiette cotisations sociales
multiplicateur: plafond sécurité sociale
composantes:
- attributs:

View File

@ -16,10 +16,10 @@ let humanFigure = decimalDigits => value => fmt(value.toFixed(decimalDigits))
export default class Results extends Component {
render() {
let {analysedSituation, pointedOutObjectives} = this.props
// On travaille sur un objectif qui est une somme de plusieurs variables, et c'est ces variables que nous affichons comme résultats
// On travaille pour l'instant sur un objectif qui est une somme de plusieurs variables, et c'est ces variables que nous affichons comme résultats. D'où ce chemin :
let explanation = R.path(['formule', 'explanation', 'explanation'])(analysedSituation)
if (!explanation) return null
console.log('explanation', explanation)
return (
<section id="results">
@ -29,12 +29,13 @@ export default class Results extends Component {
</div>
<ul>
{explanation.map(
({variableName, nodeValue, explanation: {name, type, 'non applicable si': {nodeValue: nonApplicable}, formule: {nodeValue: computedValue}}}) =>
({variableName, nodeValue, explanation: {name, type, 'non applicable si': nonApplicable, formule: {nodeValue: computedValue}}}) =>
do {
let
unsatisfied = nodeValue == null,
irrelevant = nonApplicable === true || computedValue == 0,
number = nonApplicable == false && computedValue != null,
nonApplicableValue = nonApplicable ? nonApplicable.nodeValue : false,
irrelevant = nonApplicableValue === true || computedValue == 0,
number = nonApplicableValue == false && computedValue != null,
pointedOut = pointedOutObjectives.find(objective => objective == variableName)
;<li key={name} className={classNames({unsatisfied, irrelevant, number, pointedOut})}>

View File

@ -121,7 +121,7 @@
}
#references img {
vertical-align: sub;
height: 1.2em;
height: 1.2em;
border-radius: .3em;
margin-left: .6em;
}
@ -238,6 +238,7 @@
font-weight: 600;
display: inline-block;
margin-bottom: 1em;
margin-right: 0.8em;
}
.multiplication li .key {
@ -290,10 +291,10 @@
.variable .name {
color: #5B5B73;
text-align: center;
margin-top: 1em;
padding: .05em 1em;
line-height: 1.8em;
border-radius: .4em;
margin-top: 1em;
padding: .05em 1em;
line-height: 1.8em;
border-radius: .4em;
font-weight: 600;
font-size: 90%;
border: 1px solid rgba(51, 51, 80, 0.25);
@ -316,3 +317,15 @@
.json {
font-size: 60%;
}
.barème table {
width: 80%;
text-align: left;
}
.barème table td {
padding: .1em .4em;
/*border: 1px solid #aaa;*/
}
.barème table tr:nth-child(2n) {
background: #f1f1f1
}

View File

@ -9,7 +9,7 @@ import {connect} from 'react-redux'
import mockSituation from '../engine/mockSituation.yaml'
import {START_CONVERSATION} from '../actions'
import classNames from 'classnames'
import destinataires from '../../règles/destinataires/destinataires.yaml'
import possiblesDestinataires from '../../règles/destinataires/destinataires.yaml'
import references from '../../règles/références/références.yaml'
import {capitalise0} from '../utils'
import knownMecanisms from '../engine/known-mecanisms.yaml'
@ -55,7 +55,7 @@ export default class Rule extends Component {
situationExists = !R.isEmpty(form)
let destinataire = R.path(['attributs', 'destinataire'])(rule),
destinataireData = destinataires[destinataire]
destinataireData = possiblesDestinataires[destinataire]
return (
@ -73,14 +73,20 @@ export default class Rule extends Component {
</div>
<div id="destinataire">
<h2>Destinataire</h2>
<a href={destinataireData.lien} target="_blank">
{destinataireData.image &&
<img src={require('../../règles/destinataires/' + destinataireData.image)} /> }
{!destinataireData.image &&
<div id="calligraphy">{destinataire}</div>
}
</a>
{destinataireData.nom && <div id="destinataireName">{destinataireData.nom}</div>}
{!destinataireData ?
<p>Non renseigné</p>
: <div>
<a href={destinataireData.lien} target="_blank">
{destinataireData.image &&
<img src={require('../../règles/destinataires/' + destinataireData.image)} /> }
{!destinataireData.image &&
<div id="calligraphy">{destinataire}</div>
}
</a>
{destinataireData.nom && <div id="destinataireName">{destinataireData.nom}</div>}
</div>
}
</div>
<div>
<h2>Références</h2>
@ -169,8 +175,9 @@ class Algorithm extends React.Component {
<div id="algorithm">
<section id="rule-rules" className={classNames({showValues})}>
{ do {
// TODO ce let est incompréhensible !
let [,cond] =
R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || []
R.toPairs(rule).find(([,v]) => v && v.rulePropType == 'cond') || []
cond != null &&
<section id="declenchement">
<h2>Conditions de déclenchement</h2>

View File

@ -70,17 +70,8 @@ non applicable si:
La formule est donc à ignorer.
# à venir
# barème:
# description: |
# C'est un barème en taux marginaux, mécanisme de calcul connu pour l'impôt sur le revenu.
# L'assiette est divisée en plusieurs tranches, multipliées par un taux prope à chaque tranche.
# Les tranches sont très souvent exprimées sous forme de facteurs (par exemple [1, 2, 4]) d'une variable, couramment le plafonde de la sécurité sociale.
barème:
description: |
C'est un barème en taux marginaux, mécanisme de calcul connu son utilisation dans le calcul de l'impôt sur le revenu.
L'assiette est décomposée en plusieurs tranches, qui sont multipliées par un taux spécifique.
Les tranches sont très souvent exprimées sous forme de facteurs (par exemple [1, 2, 4]) d'une variable que l'on appelle multiplicateur, par exemple le plafond de la sécurité sociale.

View File

@ -2,10 +2,10 @@ import React from 'react'
import R from 'ramda'
import classNames from 'classnames'
let treatValue = data =>
let treatValue = data => console.log('data', data) ||
data == null
? '?'
: R.is(Number)(data) ? Math.round(data) : data ? 'oui' : 'non'
: !isNaN(data) ? Math.round(+data) : data ? 'oui' : 'non'
let NodeValue = ({data}) => (
<span className={'situationValue ' + treatValue(data)}>

View File

@ -497,6 +497,97 @@ let treat = (situationGate, rule) => rawNode => {
}
}
if (k === 'barème') {
// Sous entendu : barème en taux marginaux. A étendre (avec une propriété type ?) quand les règles en contiendront d'autres.
let
val = node => node.nodeValue,
assiette = reTreat(v['assiette']),
multiplicateur = reTreat(v['multiplicateur des tranches']),
/* on réécrit en plus bas niveau les tranches :
`en-dessous de: 1`
devient
```
de: 0
à: 1
```
*/
tranches = v['tranches'].map(t =>
R.has('en-dessous de')(t) ? {de: 0, 'à': t['en-dessous de'], taux: t.taux}
: R.has('au-dessus de')(t) ? {de: t['au-dessus de'], 'à': Infinity, taux: t.taux}
: t
),
//TODO appliquer retreat() à de, à, taux pour qu'ils puissent contenir des calculs ou pour les cas où toutes les tranches n'ont pas un multiplicateur commun (ex. plafond sécurité sociale). Il faudra alors vérifier leur nullité comme ça :
/*
nulled = assiette.nodeValue == null || R.any(
R.pipe(
R.values, R.map(val), R.any(R.equals(null))
)
)(tranches),
*/
// nulled = anyNull([assiette, multiplicateur]),
nulled = val(assiette) == null || val(multiplicateur) == null,
nodeValue =
nulled ?
null
: tranches.reduce((memo, {de: min, 'à': max, taux}) =>
( val(assiette) < ( min * val(multiplicateur) ) )
? memo + 0
: memo
+ ( Math.min(val(assiette), max * val(multiplicateur)) - (min * val(multiplicateur)) )
* transformPercentage(taux)
, 0)
return {
nodeValue,
category: 'mecanism',
name: 'barème',
barème: 'en taux marginaux',
type: 'numeric',
explanation: {
assiette,
multiplicateur,
tranches
},
jsx: <Node
classes="mecanism barème"
name="barème"
value={nodeValue}
child={
<ul>
<li key="assiette">
<span className="key">assiette: </span>
<span className="value">{assiette.jsx}</span>
</li>
<li key="multiplicateur">
<span className="key">multiplicateur des tranches: </span>
<span className="value">{multiplicateur.jsx}</span>
</li>
<table className="tranches">
<thead>
<tr>
<th>Tranche de l'assiette</th>
<th>Taux</th>
</tr>
{v['tranches'].map(({'en-dessous de': maxOnly, 'au-dessus de': minOnly, de: min, 'à': max, taux}) =>
<tr key={min || minOnly}>
<td>
{ maxOnly ? 'En dessous de ' + maxOnly
: minOnly ? 'Au dessus de ' + minOnly
: `De ${min} à ${max}` }
</td>
<td> {taux} </td>
</tr>
)}
</thead>
</table>
</ul>
}
/>
}
}
if (k === 'le maximum de') {
let contenders = v.map(treat(situationGate, rule)),
contenderValues = R.pluck('nodeValue')(contenders),