🖋️ ajoute de la documentation pour publicode

pull/939/head
Johan Girod 2020-03-23 19:07:44 +01:00
parent b348b44c93
commit 577e7fc60b
4 changed files with 339 additions and 19 deletions

View File

@ -32,6 +32,8 @@ prix total:
formule: 5 * prix d'un repas
```
> [Lancer le calcul](https://publi.codes/studio?code=prix%20d'un%20repas%3A%0A%20%20formule%3A%2010%0A%0Aprix%20total%3A%0A%20%20formule%3A%205%20*%20prix%20d'un%20repas)
Il s'agit d'un langage déclaratif : comme dans une formule d'un tableur le `prix total` sera recalculé automatiquement si le prix d'un repas change. L'ordre de
définition des règles n'a pas d'importance.
@ -51,6 +53,8 @@ prix total:
formule: nombre de repas * prix d'un repas
```
> [Lancer le calcul](https://publi.codes/studio?code=prix%20d'un%20repas%3A%0A%20%20formule%3A%2010%20%E2%82%AC%2Frepas%0A%0Anombre%20de%20repas%3A%0A%20%20formule%3A%205%20repas%0A%0Aprix%20total%3A%0A%20%20formule%3A%20nombre%20de%20repas%20*%20prix%20d'un%20repas)
Le calcul est inchangé mais on a indiqué que le "prix d'un repas" s'exprime en
`€/repas` et que le "nombre de repas" est un nombre de `repas`. L'unité du prix
total est inférée automatiquement comme étant en `€`. (`€/repas` \* `repas` =
@ -73,6 +77,8 @@ prix total:
# La formule de "prix total" est invalide.
```
> [Lancer le calcul](https://publi.codes/studio?code=prix%20d'un%20repas%3A%0A%20%20formule%3A%2010%20%E2%82%AC%2Frepas%0A%0Anombre%20de%20repas%3A%0A%20%20formule%3A%205%20repas%0A%0Afrais%20de%20r%C3%A9servation%3A%0A%20%20formule%3A%201%20%E2%82%AC%2Frepas%0A%0Aprix%20total%3A%0A%20%20formule%3A%20nombre%20de%20repas%20*%20prix%20d'un%20repas%20%2B%20frais%20de%20r%C3%A9servation)
Dans l'exemple ci-dessus Publicode détecte une erreur car les termes de
l'addition ont des unités incompatibles : d'un côté on a des `€` et de l'autre
des `€/repas`. Comme dans les formules de Physique, cette incohérence d'unité
@ -84,6 +90,8 @@ prix total:
formule: nombre de repas * (prix d'un repas + frais de réservation)
```
> [Lancer le calcul](<https://publi.codes/studio?code=prix%20d'un%20repas%3A%0A%20%20formule%3A%2010%20%E2%82%AC%2Frepas%0A%0Anombre%20de%20repas%3A%0A%20%20formule%3A%205%20repas%0A%0Afrais%20de%20r%C3%A9servation%3A%0A%20%20formule%3A%201%20%E2%82%AC%2Frepas%0A%0Aprix%20total%3A%0A%20%20formule%3A%20nombre%20de%20repas%20*%20(prix%20d'un%20repas%20%2B%20frais%20de%20r%C3%A9servation)>)
> **Attention:** Il ne faut pas insérer d'espace autour de la barre oblique dans
> les unités, l'unité ~`€ / mois`~ doit être notée `€/mois`
@ -136,7 +144,9 @@ variable `taux` dans un autre espace de nom.
Les règles de calcul élémentaires sont extraites dans des "mécanismes" qui
permettent de partager la logique de calcul et de générer une page d'explication
spécifique par mécanisme. Par exemple on a un mécanisme `barème`:
spécifique par mécanisme.
Par exemple on a un mécanisme `barème`:
```yaml
impôt sur le revenu:
@ -173,19 +183,29 @@ prime . taux du bonus:
formule: 20%
```
**[Voir la liste des mécanismes](https://github.com/betagouv/mon-entreprise/blob/master/publicode/mecanism.md)**
## Applicabilité
On peut définir des conditions d'applicabilité des règles :
```yaml
ancienneté:
formule: aujourd'hui - date de début
date de début:
formule: 12/02/2020
ancienneté en fin d'année:
formule:
durée:
depuis: date de début
jusqu'à: 31/12/2020
prime de vacances:
applicable si: ancienneté > 1 an
applicable si: ancienneté en fin d'année > 1 an
formule: 200€
```
> [Lancer le calcul](https://publi.codes/studio?code=date%20de%20d%C3%A9but%3A%20%0A%20%20formule%3A%2012%2F02%2F2020%0A%20%20%0Aanciennet%C3%A9%20en%20fin%20d'ann%C3%A9e%3A%0A%20%20formule%3A%20%0A%20%20%20%20dur%C3%A9e%3A%0A%20%20%20%20%20%20%20depuis%3A%20date%20de%20d%C3%A9but%0A%20%20%20%20%20%20%20jusqu'%C3%A0%3A%2031%2F12%2F2020%0A%0Aprime%20de%20vacances%3A%0A%20%20applicable%20si%3A%20anciennet%C3%A9%20en%20fin%20d'ann%C3%A9e%20%3E%201%20an%0A%20%20formule%3A%20200%E2%82%AC)
Ici si l'ancienneté est inférieure à un an la prime de vacances ne sera pas
applicable. Les variables non applicables sont ignorées au niveau des mécanismes
(par exemple le mécanisme `somme` comptera une prime non applicable comme valant

263
publicode/mecanism.md Normal file
View File

@ -0,0 +1,263 @@
## Liste des mécanismes existants
### `une de ces conditions`
C'est un `ou` logique.
Contient une liste de conditions.
Renvoie vrai si l'une des conditions est vraie.
```yaml
age:
formule: 17 ans
mineur émancipé:
formule: oui
peut voter:
formule:
une de ces conditions:
- age > 18 ans
- mineur émancipé
```
> [Lancer le calcul](http://localhost:8080/publicodes/studio?code=date%20de%20d%C3%A9but%3A%20%0A%20%20formule%3A%2012%2F02%2F2020%0A%20%20%0Aanciennet%C3%A9%20en%20fin%20d%27ann%C3%A9e%3A%0A%20%20formule%3A%20%0A%20%20%20%20dur%C3%A9e%3A%0A%20%20%20%20%20%20%20depuis%3A%20date%20de%20d%C3%A9but%0A%20%20%20%20%20%20%20jusqu%27%C3%A0%3A%2031%2F12%2F2020%0A%0Aprime%20de%20vacances%3A%0A%20%20applicable%20si%3A%20anciennet%C3%A9%20en%20fin%20d%27ann%C3%A9e%20%3E%201%20an%0A%20%20formule%3A%20200%E2%82%AC)
### `toutes ces conditions`
C'est un `et` logique.
Contient une liste de conditions.
Renvoie vrai si toutes les conditions vraies.
### `produit`
C'est une multiplication un peu améliorée, très utile pour exprimer les cotisations.
Sa propriété `assiette` est multipliée par un pourcentage, `taux`, ou par un `facteur` quand ce nom est plus approprié.
La multiplication peut être plafonnée : ce plafond sépare l'assiette en deux, et la partie au-dessus du plafond est tout simplement ignorée. Dans ce cas, elle se comporte comme une barème en taux marginaux à deux tranches, la deuxième au taux nul et allant de `plafond` à l'infini.
```yaml
plafond sécurité sociale:
formule: 3428 €/mois
assiette cotisation:
formule: 2300 €/mois
chômage:
formule:
produit:
assiette: assiette cotisation
plafond: 400% * plafond sécurité sociale
taux: 4.05%
```
[Lancer le calcul](https://publi.codes/studio?code=plafond%20s%C3%A9curit%C3%A9%20sociale%3A%0A%20%20formule%3A%203428%20%E2%82%AC%2Fmois%0A%0Aassiette%20cotisation%3A%20%0A%20%20formule%3A%202300%20%E2%82%AC%2Fmois%0A%0Ach%C3%B4mage%3A%20%0A%20%20formule%3A%0A%20%20%20%20produit%3A%20%0A%20%20%20%20%20%20assiette%3A%20assiette%20cotisation%0A%20%20%20%20%20%20plafond%3A%20400%25%20*%20plafond%20s%C3%A9curit%C3%A9%20sociale%0A%20%20%20%20%20%20taux%3A%204.05%25%0A%20%20%20%20%20%20)
### `variations`
Contient une liste de conditions (`si`) et leurs conséquences associées (`alors`).
Pour la première condition vraie dans la liste, on retient la valeur qui lui est associée.
Si aucune condition n'est vraie, alors ce mécanisme renvoie implicitement `non applicable`
```yaml
taux réduit:
formule: oui
taux allocation familiales:
formule:
variations:
- si: taux réduit
alors: 3.45%
- sinon: 5.25%
```
> [Lancer le calcul](https://publi.codes/studio?code=taux%20r%C3%A9duit%3A%0A%20%20formule%3A%20oui%0A%0Ataux%20allocation%20familiales%3A%0A%20%20formule%3A%0A%20%20%20%20variations%3A%0A%20%20%20%20%20%20-%20si%3A%20taux%20r%C3%A9duit%0A%20%20%20%20%20%20%20%20alors%3A%203.45%25%0A%20%20%20%20%20%20-%20sinon%3A%205.25%25)
Ce mécanisme peut aussi être utilisé au sein d'un mécanisme compatible, tel que la produit ou le barème.
```yaml
assiette cotisation:
formule: 2300 €/mois
taux réduit:
formule: oui
allocation familiales:
formule:
produit:
assiette: assiette cotisation
variations:
- si: taux réduit
alors:
taux: 3.45%
- sinon:
taux: 5.25%
```
> [Lancer le calcul](http://localhost:8080/publicodes/studio?code=date%20de%20d%C3%A9but%3A%20%0A%20%20formule%3A%2012%2F02%2F2020%0A%20%20%0Aanciennet%C3%A9%20en%20fin%20d%27ann%C3%A9e%3A%0A%20%20formule%3A%20%0A%20%20%20%20dur%C3%A9e%3A%0A%20%20%20%20%20%20%20depuis%3A%20date%20de%20d%C3%A9but%0A%20%20%20%20%20%20%20jusqu%27%C3%A0%3A%2031%2F12%2F2020%0A%0Aprime%20de%20vacances%3A%0A%20%20applicable%20si%3A%20anciennet%C3%A9%20en%20fin%20d%27ann%C3%A9e%20%3E%201%20an%0A%20%20formule%3A%20200%E2%82%AC)
### `somme`
C'est tout simplement la somme de chaque terme de la liste. Si un des terme
n'est pas applicable, il vaut zéro.
```yaml
a:
formule: 50 €
b:
applicable si: non
formule: 20 €
somme:
formule:
somme:
- a
- b
- 40 €
```
> [Lancer le calcul](https://publi.codes/studio?code=a%3A%20%0A%20%20formule%3A%2050%20%E2%82%AC%0A%0Ab%3A%20%0A%20%20applicable%20si%3A%20non%0A%20%20formule%3A%2020%20%E2%82%AC%0A%0Asomme%3A%0A%20%20formule%3A%0A%20%20%20%20somme%3A%0A%20%20%20%20%20%20-%20a%0A%20%20%20%20%20%20-%20b%0A%20%20%20%20%20%20-%2040%20%E2%82%AC)
### `le maximum de`
Renvoie la valeur numérique de la liste de propositions fournie qui est la plus grande.
Il est conseillé de renseigner une description de chaque proposition par exemple quand elles représentent des méthodes de calcul alternatives.
### `le minimum de`
Renvoie l'élément de la liste de propositions fournie qui a la plus petite valeur.
Ces propositions doivent avoir un mécanisme de calcul ou être une valeur numérique.
Il est conseillé de renseigner une description de chaque proposition par exemple quand elles représentent des méthodes de calcul alternatives parmi lesquelles il faut en choisir une.
### `arrondi`
Arrondi à l'entier le plus proche, ou à une précision donnée.
```yaml
a:
formule: 12.45
arrondi:
formule:
arrondi:
valeur: a
décimales: 1
```
### `régularisation`
Permet de régulariser progressivement un calcul de cotisation par rapport à une
variable temporelle.
```yaml
brut:
formule:
somme:
- 2000 €/mois | du 01/01/2020 | au 31/05/2020
- 4000 €/mois | du 01/06/2020 | au 31/12/2020
cotisation:
formule:
régularisation:
règle:
produit:
assiette: brut
plafond: 3000€/mois
taux: 10%
valeurs cumulées:
- brut
cotisation en 2020:
formule: cotisation | du 01/01/2020 | au 31/12/2020
```
[Lancer le calcul](https://publi.codes/studio?code=brut%3A%0A%20%20formule%3A%0A%20%20%20%20somme%3A%0A%20%20%20%20%20%20-%202000%20%E2%82%AC%2Fmois%20%7C%20du%2001%2F01%2F2020%20%7C%20au%2031%2F05%2F2020%0A%20%20%20%20%20%20-%204000%20%E2%82%AC%2Fmois%20%7C%20du%2001%2F06%2F2020%20%7C%20au%2031%2F12%2F2020%0A%0Acotisation%3A%0A%20%20formule%3A%20%0A%20%20%20%20r%C3%A9gularisation%3A%0A%20%20%20%20%20%20r%C3%A8gle%3A%0A%20%20%20%20%20%20%20%20produit%3A%0A%20%20%20%20%20%20%20%20%20%20assiette%3A%20brut%0A%20%20%20%20%20%20%20%20%20%20plafond%3A%203000%E2%82%AC%2Fmois%0A%20%20%20%20%20%20%20%20%20%20taux%3A%2010%25%0A%20%20%20%20%20%20valeurs%20cumul%C3%A9es%3A%0A%20%20%20%20%20%20%20%20-%20brut%0A%0Acotisation%20en%202020%3A%0A%20%20formule%3A%0A%20%20%20%20cotisation%20%7C%20du%2001%2F01%2F2020%20%7C%20au%2031%2F12%2F2020%0A)
### `recalcul`
Relance le calcul d'une règle dans une situation différente de la situation
courante. Permet par exemple de calculer le montant des cotisations au
niveau du SMIC, même si le salaire est plus élevé dans la situation
actuelle.
### `barème`
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 souvent exprimées sous forme de facteurs d'une variable
que l'on appelle `multiplicateur`, par exemple `1 x le plafond de la sécurité sociale`.
### `grille`
C'est un barème sous la forme d'une grille de correspondance. C'est le
mécanisme de calcul de l'impôt neutre, aussi appelé impôt non personnalisé.
Il est composé de tranches qui se suivent. Il suffit de trouver l'assiette
qui correspond à la tranche, et de selectionner le montant associé à
l'assiette.
### `taux progressif`
Ce mécanisme permet de calculer un taux progressif. On spécifie pour chaque
tranche le plafond et le taux associé. Le taux effectif renvoyé est calculé
en lissant la différence de taux entre la borne inférieure et supérieure de
l'assiette
> Par exemple, si nous nous avons les tranches suivantes :
- taux: 50% / plafond: 0
- taux: 100% / plafond: 1000
> Pour une assiette de 500, le taux retourné sera 75%, car il correspond au
> taux situé à la moitié de la tranche correspondante.
### `composantes`
Beaucoup de cotisations sont composées de deux parties qui partagent la méthode de calcul mais diffèrent par des paramètres différents.
Pour ne pas définir deux variables presque redondantes, on utilise le mécanisme de composante. Il se comportera comme une somme dans les calculs, mais son affichage sur les pages /règle sera adapté.
Il est même possible, pour les mécanismes `barème` et `produit` de garder en commun un paramètre comme l'assiette, puis de déclarer des composantes pour le taux.
> L'exemple le plus courant de composantes, c'est la distinction part employeur, part salarié (ex. retraite AGIRC).
### `allègement`
Permet de réduire le montant d'une variable.
Très utilisé dans le contexte des impôts.
### `encadrement`
Permet d'ajouter un plafond et/ou un plancher à une valeur.
### `durée`
Permet d'obtenir le nombre de jours entre deux dates
### `synchronisation`
Pour éviter trop de saisies à l'utilisateur, certaines informations sont
récupérées à partir de ce que l'on appelle des API. Ce sont des services
auxquels ont fait appel pour obtenir des informations sur un sujet précis.
Par exemple, l'État français fournit gratuitement l'API géo, qui permet à
partir du nom d'une ville, d'obtenir son code postal, son département, la
population etc.
Ce mécanismes `synchronisation` permet de faire le lien entre les règles de
notre système et les réponses de ces API.
### `inversion numérique`
La formule de calcul de cette variable n'est pas connue, souvent elle n'a même pas de sens. Mais le mécanisme `inversion` indique qu'elle peut être _estimée_ à partir de l'un des _objectifs_ listés sous l'attribut `avec`. Il faut alors renseigner une valeur cible pour cet objectif.
Voilà comment ça marche : on va donner à la variable une valeur au hasard, calculer _l'objectif_, puis grâce à des calculs savants améliorer notre choix jusqu'à ce que l'écart entre le calcul et la valeur cible devienne satisfaisant.
Concrètement, si l'on demande au moteur (même indirectement) la valeur d'une variable qui a pour formule une inversion, il va vérifier qu'une des possibilités d'inversion a bien une valeur calculée ou saisie, et procéder à l'inversion décrite plus haut à partir de celle-ci. Sinon, ces possibilités d'inversions seront listées comme manquantes.

View File

@ -46,11 +46,11 @@ export default function Landing() {
<ul>
<li>
<a href="https://github.com/betagouv/publicodes/wiki">
{emoji('📖 ')} Lisez la documentation
{emoji('📖 ')} Lire la documentation
</a>
</li>
<li>
<Link to="/studio"> Essayez le langage </Link>
<Link to="/studio"> Essayer le langage </Link>
</li>
</ul>
<h2>Projets phares</h2>

View File

@ -3,7 +3,8 @@ import douche from '!!raw-loader!./exemples/douche.yaml'
import { ControlledEditor } from '@monaco-editor/react'
import Engine from 'Engine/react'
import { safeLoad } from 'js-yaml'
import React, { useEffect, useState } from 'react'
import { last } from 'ramda'
import React, { useCallback, useEffect, useState } from 'react'
import emoji from 'react-easy-emoji'
import { useLocation } from 'react-router'
import styled from 'styled-components'
@ -28,15 +29,23 @@ d:
`
export default function Studio() {
const { search } = useLocation()
const currentExample = new URLSearchParams(search ?? '').get('exemple')
const search = new URLSearchParams(useLocation().search ?? '')
const currentExample = search.get('exemple')
const code = search.get('code')
const [editorValue, setEditorValue] = useState(
currentExample && Object.keys(examples).includes(currentExample)
code
? code
: currentExample && Object.keys(examples).includes(currentExample)
? examples[currentExample]
: initialInput
)
const [targets, setTargets] = useState<string[]>([])
const [rules, setRules] = useState(editorValue)
const handleShare = useCallback(() => {
navigator.clipboard.writeText(
`https://publi.codes/studio?code=${encodeURIComponent(editorValue)}`
)
}, [editorValue])
useEffect(() => {
try {
@ -76,6 +85,7 @@ export default function Studio() {
<Results
targets={targets}
onClickUpdate={() => setRules(editorValue)}
onClickShare={handleShare}
/>
</section>
</Layout>
@ -84,11 +94,18 @@ export default function Studio() {
)
}
export const Results = ({ targets, onClickUpdate }) => {
const [currentTarget, setCurrentTarget] = useState('')
const rule = targets.includes(currentTarget) ? currentTarget : targets[0]
export const Results = ({ targets, onClickUpdate, onClickShare }) => {
const [rule, setCurrentTarget] = useState()
const currentTarget = rule ?? last(targets)
const error = Engine.useError()
const analysis = Engine.useEvaluation(rule)
// EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker
// console.warn
const warnings: string[] = []
const originalWarn = console.warn
console.warn = warning => warnings.push(warning)
const analysis = Engine.useEvaluation(currentTarget)
console.warn = originalWarn
return error !== null ? (
<div
css={`
@ -124,24 +141,44 @@ export const Results = ({ targets, onClickUpdate }) => {
`}
>
{targets.map(target => (
<option key={target} value={target}>
<option
key={target}
value={target}
selected={currentTarget === target}
>
{target}
</option>
))}
</select>
<br />
<br />
<button className="ui__ button small" onClick={onClickUpdate}>
{emoji('▶️')} Recalculer
</button>
<div className="ui__ answer-group">
<button className="ui__ plain button small" onClick={onClickUpdate}>
{emoji('▶️')} Calculer
</button>
<button className="ui__ button small" onClick={onClickShare}>
{emoji('🔗')} Copier le lien
</button>
</div>
</div>
{warnings.map(warning => (
<div
css={`
background: lightyellow;
padding: 20px;
border-radius: 5px;
`}
>
{nl2br(warning)}
</div>
))}
{analysis ? (
<div>
<h2>Résultats</h2>
{analysis.isApplicable === false ? (
<>{emoji('❌')} Cette règle n'est pas applicable</>
) : (
<Engine.Evaluation expression={rule} />
<Engine.Evaluation expression={currentTarget} />
)}
</div>
) : null}