mon-entreprise/source/engine/rules.js

268 lines
6.8 KiB
JavaScript

// Séparation artificielle, temporaire, entre ces deux types de règles
import rawRules from 'Règles/base.yaml'
import translations from 'Règles/externalized.yaml'
import {
assoc,
mapObjIndexed,
chain,
has,
pipe,
toPairs,
map,
fromPairs,
split,
join,
dropLast,
take,
propEq,
reduce,
when,
is,
props,
identity,
path,
reject,
reduced,
range,
last,
trim
} from 'ramda'
import possibleVariableTypes from './possibleVariableTypes.yaml'
import marked from './marked'
import { capitalise0 } from '../utils'
import formValueTypes from 'Components/conversation/formValueTypes'
// TODO - should be in UI, not engine
import taux_versement_transport from 'Règles/taux-versement-transport.json'
// console.log('rawRules', rawRules.map(({espace, nom}) => espace + nom))
/***********************************
Méthodes agissant sur une règle */
// Enrichissement de la règle avec des informations évidentes pour un lecteur humain
export let enrichRule = (rule, sharedData = {}) => {
try {
let type = possibleVariableTypes.find(t => has(t, rule) || rule.type === t),
name = rule['nom'],
title = capitalise0(rule['titre'] || name),
ns = rule['espace'],
data = rule['données'] ? sharedData[rule['données']] : null,
dottedName = buildDottedName(rule),
subquestionMarkdown = rule['sous-question'],
subquestion = subquestionMarkdown && marked(subquestionMarkdown),
defaultValue = rule['par défaut'],
examples = rule['exemples'],
icon = rule['icônes']
return {
...rule,
type,
name,
title,
ns,
data,
dottedName,
subquestion,
defaultValue,
raw: rule,
examples,
icon
}
} catch (e) {
throw new Error('Problem enriching ' + JSON.stringify(rule))
}
}
let buildDottedName = rule =>
rule['espace'] ? [rule['espace'], rule['nom']].join(' . ') : rule['nom']
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
// comme dans sa formule
export let disambiguateExampleSituation = (rules, rule) =>
pipe(
toPairs,
map(([k, v]) => [disambiguateRuleReference(rules, rule, k), v]),
fromPairs
)
export let hasKnownRuleType = rule => rule && enrichRule(rule).type
export let splitName = split(' . '),
joinName = join(' . ')
export let parentName = pipe(
splitName,
dropLast(1),
joinName
)
export let nameLeaf = pipe(
splitName,
last
)
export let encodeRuleName = name =>
name.replace(/\s\.\s/g, '--').replace(/\s/g, '-')
export let decodeRuleName = name =>
name.replace(/--/g, ' . ').replace(/-/g, ' ')
/* Les variables peuvent être exprimées dans la formule d'une règle relativement à son propre espace de nom, pour une plus grande lisibilité. Cette fonction résoud cette ambiguité.
*/
export let disambiguateRuleReference = (
allRules,
{ ns, name },
partialName
) => {
let fragments = ns ? [...ns.split(' . '), name] : [], // ex. [CDD . événements . rupture]
pathPossibilities = range(0, fragments.length + 1) // -> [ [CDD . événements . rupture], [CDD . événements], [CDD] ]
.map(nbEl => take(nbEl)(fragments))
.reverse(),
found = reduce(
(res, path) =>
when(is(Object), reduced)(
findRuleByDottedName(allRules, [...path, partialName].join(' . '))
),
null,
pathPossibilities
)
return (
(found && found.dottedName) ||
do {
throw new Error(
`OUUUUPS la référence '${partialName}' dans la règle '${name}' est introuvable dans la base`
)
}
)
}
export let collectDefaults = pipe(
map(props(['dottedName', 'defaultValue'])),
reject(([, v]) => v === undefined),
fromPairs
)
/****************************************
Méthodes de recherche d'une règle */
export let findRuleByName = (allRules, query) =>
allRules.find(({ name }) => name === query)
export let findRulesByName = (allRules, query) =>
allRules.filter(({ name }) => name === query)
export let searchRules = searchInput =>
rules
.filter(
rule =>
rule &&
hasKnownRuleType(rule) &&
JSON.stringify(rule)
.toLowerCase()
.indexOf(searchInput) > -1
)
.map(enrichRule)
export let findRuleByDottedName = (allRules, dottedName) => {
return allRules.find(rule => rule.dottedName == dottedName)
}
export let findRule = (rules, nameOrDottedName) =>
nameOrDottedName.includes(' . ')
? findRuleByDottedName(rules, nameOrDottedName)
: findRuleByName(rules, nameOrDottedName)
export let findRuleByNamespace = (allRules, ns) =>
allRules.filter(propEq('ns', ns))
/*********************************
Autres */
export let queryRule = rule => query => path(query.split(' . '))(rule)
var findObjectByLabel = function(obj, label) {
if (obj.label === label) {
return obj
}
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
var foundLabel = findObjectByLabel(obj[i], label)
if (foundLabel) {
return foundLabel
}
}
}
return null
}
// Redux-form stores the form values as a nested object
// This helper makes a dottedName => value Map
export let nestedSituationToPathMap = situation => {
let rec = (o, currentPath) =>
typeof o === 'object'
? chain(([k, v]) => rec(v, [...currentPath, trim(k)]), toPairs(o))
: [[currentPath.join(' . '), o + '']]
return fromPairs(rec(situation, []))
}
export let formatInputs = (flatRules, pathValueMap) =>
mapObjIndexed((value, path) => {
// Our situationGate retrieves data from the "conversation" form
// The search below is to apply input conversions such as replacing "," with "."
if (name.startsWith('sys.')) return null
let rule = findRuleByDottedName(flatRules, path),
format = rule ? formValueTypes[rule.format] : null,
pre = format && format.validator.pre ? format.validator.pre : identity
return pre(value)
}, pathValueMap)
/* Traduction */
export let translateAll = (translations, flatRules) => {
let translationsOf = rule => translations[buildDottedName(rule)],
translateProp = (lang, translation) => (rule, prop) => {
let propTrans = translation[prop + '.' + lang]
if (prop === 'suggestions' && propTrans)
return assoc(
'suggestions',
pipe(
toPairs,
map(([key, translatedKey]) => [
translatedKey,
rule.suggestions[key]
]),
fromPairs
)(propTrans),
rule
)
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
}
let targets = [
'titre',
'description',
'question',
'sous-question',
'résumé',
'suggestions'
]
return map(translateRule('en', translations, targets), flatRules)
}
// On enrichit la base de règles avec des propriétés dérivées de celles du YAML
export let rules = translateAll(translations, rawRules).map(rule =>
enrichRule(rule, { taux_versement_transport })
)
export let rulesFr = rawRules.map(rule =>
enrichRule(rule, { taux_versement_transport })
)