2019-07-16 12:53:10 +00:00
import { parseUnit } from 'Engine/units'
2020-02-04 17:35:21 +00:00
import rawRules from 'Publicode/rules'
import { assoc , chain , dropLast , filter , fromPairs , is , isNil , join , last , map , path , pipe , propEq , props , range , reduce , reduced , reject , split , take , toPairs , trim , when } from 'ramda'
2020-02-04 16:05:59 +00:00
import translations from '../locales/rules-en.yaml'
2018-09-04 14:23:05 +00:00
// TODO - should be in UI, not engine
2019-07-16 12:53:10 +00:00
import { capitalise0 , coerceArray } from '../utils'
2019-11-28 11:03:23 +00:00
import { syntaxError , warning } from './error'
2017-09-18 07:29:26 +00:00
2017-01-26 10:27:24 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2019-04-10 09:42:55 +00:00
Functions working on one rule * /
2017-01-26 10:27:24 +00:00
2019-04-03 15:40:51 +00:00
export let enrichRule = rule => {
2019-07-16 12:53:10 +00:00
try {
2019-10-11 15:01:11 +00:00
const dottedName = rule . dottedName || rule . nom
const name = nameLeaf ( dottedName )
2019-11-28 11:03:23 +00:00
let unit = rule . unité && parseUnit ( rule . unité )
let defaultUnit =
rule [ 'unité par défaut' ] && parseUnit ( rule [ 'unité par défaut' ] )
if ( defaultUnit && unit ) {
warning (
dottedName ,
2019-12-13 16:22:18 +00:00
'Le paramètre `unité` est plus contraignant que `unité par défaut`.' ,
2019-11-28 11:03:23 +00:00
'Si vous souhaitez que la valeur de votre variable soit toujours la même unité, gardez `unité`'
)
}
2019-07-16 12:53:10 +00:00
return {
... rule ,
2019-10-11 15:01:11 +00:00
dottedName ,
name ,
2019-12-16 17:47:15 +00:00
type : rule . type ,
2019-10-11 15:01:11 +00:00
title : capitalise0 ( rule [ 'titre' ] || name ) ,
2019-07-16 12:53:10 +00:00
defaultValue : rule [ 'par défaut' ] ,
examples : rule [ 'exemples' ] ,
icons : rule [ 'icônes' ] ,
summary : rule [ 'résumé' ] ,
2019-11-28 11:03:23 +00:00
unit ,
defaultUnit
2019-07-16 12:53:10 +00:00
}
} catch ( e ) {
2019-11-28 11:03:23 +00:00
syntaxError (
rule . dottedName || rule . nom ,
'Problème dans la lecture des champs de la règle' ,
e
)
2019-07-16 12:53:10 +00:00
}
}
2017-01-26 10:27:24 +00:00
2018-06-13 08:26:27 +00:00
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
// comme dans sa formule
2018-01-03 15:54:19 +00:00
export let disambiguateExampleSituation = ( rules , rule ) =>
2019-07-16 12:53:10 +00:00
pipe (
toPairs ,
map ( ( [ k , v ] ) => [ disambiguateRuleReference ( rules , rule , k ) , v ] ) ,
fromPairs
)
2017-12-08 14:12:41 +00:00
2019-07-16 12:53:10 +00:00
export let hasKnownRuleType = rule => rule && enrichRule ( rule ) . type
2017-01-26 10:27:24 +00:00
2019-07-16 12:53:10 +00:00
export let splitName = split ( ' . ' ) ,
joinName = join ( ' . ' )
2017-01-26 10:27:24 +00:00
2019-11-28 11:03:23 +00:00
export let parentName = pipe ( splitName , dropLast ( 1 ) , joinName )
export let nameLeaf = pipe ( splitName , last )
2017-01-26 10:27:24 +00:00
2018-02-12 14:53:07 +00:00
export let encodeRuleName = name =>
2019-09-11 14:58:18 +00:00
encodeURI (
name
. replace ( /\s\.\s/g , '/' )
. replace ( /-/g , '\u2011' ) // replace with a insecable tiret to differenciate from space
. replace ( /\s/g , '-' )
)
2018-02-12 14:53:07 +00:00
export let decodeRuleName = name =>
2019-09-11 14:58:18 +00:00
decodeURI (
name
. replace ( /\//g , ' . ' )
. replace ( /-/g , ' ' )
. replace ( /\u2011/g , '-' )
)
2017-05-09 14:33:35 +00:00
2018-09-19 08:14:18 +00:00
export let ruleParents = dottedName => {
2019-07-16 12:53:10 +00:00
let fragments = splitName ( dottedName ) // dottedName ex. [CDD . événements . rupture]
return range ( 1 , fragments . length )
. map ( nbEl => take ( nbEl ) ( fragments ) )
. reverse ( ) // -> [ [CDD . événements . rupture], [CDD . événements], [CDD] ]
}
2017-04-24 18:03:38 +00:00
/ * L e s v a r i a b l e s p e u v e n t ê t r e e x p r i m é e s d a n s l a f o r m u l e d ' u n e r è g l e r e l a t i v e m e n t à s o n p r o p r e e s p a c e d e n o m , p o u r u n e p l u s g r a n d e l i s i b i l i t é . C e t t e f o n c t i o n r é s o u d c e t t e a m b i g u i t é .
2018-11-19 16:55:36 +00:00
* /
2018-01-03 15:54:19 +00:00
export let disambiguateRuleReference = (
2019-07-16 12:53:10 +00:00
allRules ,
{ dottedName , name } ,
partialName
2018-01-03 15:54:19 +00:00
) => {
2019-07-16 12:53:10 +00:00
let pathPossibilities = [
2019-11-04 13:07:19 +00:00
[ ] , // the top level namespace
... ruleParents ( dottedName ) , // the parents namespace
splitName ( dottedName ) // the rule's own namespace
] ,
2019-07-16 12:53:10 +00:00
found = reduce (
2019-09-11 08:04:19 +00:00
( res , path ) => {
let dottedNameToCheck = [ ... path , partialName ] . join ( ' . ' )
2019-11-28 11:03:23 +00:00
return when (
is ( Object ) ,
reduced
) ( findRuleByDottedName ( allRules , dottedNameToCheck ) )
2019-09-11 08:04:19 +00:00
} ,
2019-07-16 12:53:10 +00:00
null ,
pathPossibilities
)
2018-01-03 15:54:19 +00:00
2019-11-04 13:07:19 +00:00
if ( found ? . dottedName ) {
2019-09-11 08:04:19 +00:00
return found . dottedName
}
throw new Error (
` OUUUUPS la référence ' ${ partialName } ' dans la règle ' ${ name } ' est introuvable dans la base `
2019-07-16 12:53:10 +00:00
)
}
2017-03-16 18:30:30 +00:00
2018-01-08 15:07:26 +00:00
export let collectDefaults = pipe (
2019-07-16 12:53:10 +00:00
map ( props ( [ 'dottedName' , 'defaultValue' ] ) ) ,
reject ( ( [ , v ] ) => v === undefined ) ,
fromPairs
)
2017-11-22 09:17:22 +00:00
2017-01-26 10:27:24 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Méthodes de recherche d ' une règle * /
2016-12-07 18:08:10 +00:00
2018-02-12 14:53:07 +00:00
export let findRuleByName = ( allRules , query ) =>
2019-07-16 12:53:10 +00:00
( Array . isArray ( allRules ) ? allRules : Object . values ( allRules ) ) . find (
( { name } ) => name === query
)
2018-02-12 14:53:07 +00:00
export let findRulesByName = ( allRules , query ) =>
2019-07-16 12:53:10 +00:00
( Array . isArray ( allRules ) ? allRules : Object . values ( allRules ) ) . filter (
( { name } ) => name === query
)
2018-09-04 14:23:05 +00:00
2019-06-13 16:17:22 +00:00
export let findRuleByDottedName = ( allRules , dottedName ) =>
2019-07-16 12:53:10 +00:00
Array . isArray ( allRules )
? allRules . find ( rule => rule . dottedName == dottedName )
: allRules [ dottedName ]
2017-03-01 19:27:35 +00:00
2018-02-21 17:40:45 +00:00
export let findRule = ( rules , nameOrDottedName ) =>
2019-07-16 12:53:10 +00:00
nameOrDottedName . includes ( ' . ' )
? findRuleByDottedName ( rules , nameOrDottedName )
: findRuleByName ( rules , nameOrDottedName )
2018-02-21 17:40:45 +00:00
2018-01-24 18:07:55 +00:00
export let findRuleByNamespace = ( allRules , ns ) =>
2019-10-11 15:01:11 +00:00
allRules . filter ( rule => parentName ( rule . dottedName ) === ns )
2018-01-24 18:07:55 +00:00
2017-03-01 19:27:35 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2018-01-03 15:54:19 +00:00
Autres * /
2017-03-01 19:27:35 +00:00
2019-07-16 12:53:10 +00:00
export let queryRule = rule => query => path ( query . split ( ' . ' ) ) ( rule )
2018-02-06 16:02:13 +00:00
2018-06-06 07:07:11 +00:00
export let nestedSituationToPathMap = situation => {
2019-07-16 12:53:10 +00:00
if ( situation == undefined ) return { }
let rec = ( o , currentPath ) =>
typeof o === 'object'
? chain ( ( [ k , v ] ) => rec ( v , [ ... currentPath , trim ( k ) ] ) , toPairs ( o ) )
: [ [ currentPath . join ( ' . ' ) , o + '' ] ]
2018-06-06 07:07:11 +00:00
2019-07-16 12:53:10 +00:00
return fromPairs ( rec ( situation , [ ] ) )
}
2018-02-26 14:25:47 +00:00
/* Traduction */
2020-01-22 17:33:41 +00:00
const translateContrôle = ( prop , rule , translation , lang ) =>
assoc (
'contrôles' ,
rule . contrôles . map ( ( control , i ) => ( {
... control ,
message : translation [ ` ${ prop } . ${ i } . ${ lang } ` ] ? . replace (
/^\[automatic\] / ,
''
)
} ) ) ,
rule
)
const translateSuggestion = ( prop , rule , translation , lang ) =>
assoc (
'suggestions' ,
Object . entries ( rule . suggestions ) . reduce (
( acc , [ name , value ] ) => ( {
... acc ,
[ translation [ ` ${ prop } . ${ name } . ${ lang } ` ] ? . replace (
/^\[automatic\] / ,
''
) ] : value
} ) ,
{ }
) ,
rule
)
2018-02-26 14:25:47 +00:00
2020-01-23 10:35:16 +00:00
export const attributesToTranslate = [
'titre' ,
'description' ,
'question' ,
'résumé' ,
'suggestions' ,
'contrôles' ,
'note'
]
2018-02-26 14:25:47 +00:00
export let translateAll = ( translations , flatRules ) => {
2019-10-11 15:01:11 +00:00
let translationsOf = rule => translations [ rule . dottedName ] ,
2019-07-16 12:53:10 +00:00
translateProp = ( lang , translation ) => ( rule , prop ) => {
2020-01-22 17:33:41 +00:00
if ( prop === 'contrôles' && rule ? . contrôles ) {
return translateContrôle ( prop , rule , translation , lang )
}
if ( prop === 'suggestions' && rule ? . suggestions ) {
return translateSuggestion ( prop , rule , translation , lang )
2020-01-21 18:29:43 +00:00
}
2020-01-22 17:33:41 +00:00
let propTrans = translation [ prop + '.' + lang ]
propTrans = propTrans ? . replace ( /^\[automatic\] / , '' )
2019-07-16 12:53:10 +00:00
return propTrans ? assoc ( prop , propTrans , rule ) : rule
} ,
translateRule = ( lang , translations , props ) => rule => {
let ruleTrans = translationsOf ( rule )
return ruleTrans
? reduce ( translateProp ( lang , ruleTrans ) , rule , props )
: rule
}
2020-01-23 10:35:16 +00:00
return map (
translateRule ( 'en' , translations , attributesToTranslate ) ,
flatRules
)
2019-07-16 12:53:10 +00:00
}
2018-09-04 14:23:05 +00:00
2019-11-19 14:53:15 +00:00
const rulesToList = rulesObject =>
Object . entries ( rulesObject ) . map ( ( [ dottedName , rule ] ) => ( {
dottedName ,
... rule
} ) )
export const buildFlatRules = rulesObject =>
rulesToList ( rulesObject ) . map ( enrichRule )
2019-10-11 15:01:11 +00:00
2018-09-04 14:23:05 +00:00
// On enrichit la base de règles avec des propriétés dérivées de celles du YAML
2019-11-19 14:53:15 +00:00
export let rules = translateAll ( translations , rulesToList ( rawRules ) ) . map ( rule =>
2019-07-16 12:53:10 +00:00
enrichRule ( rule )
)
2019-11-19 14:53:15 +00:00
export let rulesFr = buildFlatRules ( rawRules )
2018-11-26 17:09:14 +00:00
2019-11-07 11:34:03 +00:00
export let findParentDependencies = ( rules , rule ) => {
2019-07-16 12:53:10 +00:00
// A parent dependency means that one of a rule's parents is not just a namespace holder, it is a boolean question. E.g. is it a fixed-term contract, yes / no
// When it is resolved to false, then the whole branch under it is disactivated (non applicable)
// It lets those children omit obvious and repetitive parent applicability tests
let parentDependencies = ruleParents ( rule . dottedName ) . map ( joinName )
return pipe (
map ( parent => findRuleByDottedName ( rules , parent ) ) ,
reject ( isNil ) ,
2019-11-07 11:34:03 +00:00
filter (
2019-07-16 12:53:10 +00:00
//Find the first "calculable" parent
2020-01-21 18:29:43 +00:00
( { question , unit , formule } ) =>
2019-07-02 13:54:02 +00:00
( question && ! unit && ! formule ) ||
2019-11-04 13:07:19 +00:00
( question && formule ? . [ 'une possibilité' ] !== undefined ) ||
2019-11-05 10:49:04 +00:00
( typeof formule === 'string' && formule . includes ( ' = ' ) ) ||
formule === 'oui' ||
2019-11-12 13:18:48 +00:00
formule === 'non' ||
formule ? . [ 'une de ces conditions' ] ||
formule ? . [ 'toutes ces conditions' ]
2019-07-16 12:53:10 +00:00
)
) ( parentDependencies )
}
2019-07-03 15:33:46 +00:00
export let getRuleFromAnalysis = analysis => dottedName => {
2019-07-16 12:53:10 +00:00
if ( ! analysis ) {
throw new Error ( "[getRuleFromAnalysis] The analysis can't be nil !" )
}
2019-11-05 17:56:07 +00:00
2019-07-16 12:53:10 +00:00
let rule = coerceArray ( analysis ) // In some simulations, there are multiple "branches" : the analysis is run with e.g. 3 different input situations
. map (
analysis =>
2019-11-04 13:07:19 +00:00
analysis . cache [ dottedName ] ? . explanation || // the cache stores a reference to a variable, the variable is contained in the 'explanation' attribute
analysis . targets . find ( propEq ( 'dottedName' , dottedName ) )
2019-07-16 12:53:10 +00:00
)
. filter ( Boolean ) [ 0 ]
2019-11-07 11:34:03 +00:00
if ( process . env . NODE _ENV !== 'production' && ! rule ) {
console . warn ( ` [getRuleFromAnalysis] Unable to find the rule ${ dottedName } ` )
2019-07-16 12:53:10 +00:00
}
2019-07-03 15:33:46 +00:00
2019-07-16 12:53:10 +00:00
return rule
}