mon-entreprise/source/engine/generateQuestions.js

167 lines
5.4 KiB
JavaScript

import React from 'react'
import R from 'ramda'
import Explicable from 'Components/conversation/Explicable'
import Question from 'Components/conversation/Question'
import Input from 'Components/conversation/Input'
import Select from 'Components/conversation/select/Select'
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
import formValueTypes from 'Components/conversation/formValueTypes'
import {analyseSituation} from './traverse'
import {rules, findRuleByDottedName} from './rules'
import {collectNodeMissing, evaluateNode} from './evaluation'
/*
COLLECTE DES VARIABLES MANQUANTES
*********************************
on collecte les variables manquantes : celles qui sont nécessaires pour
remplir les objectifs de la simulation (calculer des cotisations) mais qui n'ont pas
encore été renseignées
TODO perf : peut-on le faire en même temps que l'on traverse l'AST ?
Oui sûrement, cette liste se complète en remontant l'arbre. En fait, on le fait déjà pour nodeValue,
et quand nodeValue vaut null, c'est qu'il y a des missingVariables ! Il suffit donc de remplacer les
null par un tableau, et d'ailleurs utiliser des fonction d'aide pour mutualiser ces tests.
missingVariables: {variable: [objectives]}
*/
// On peut travailler sur une somme, les objectifs sont alors les variables de cette somme.
// Ou sur une variable unique ayant une formule ou une conodition 'non applicable si', elle est elle-même le seul objectif
export let getObjectives = (situationGate, root, parsedRules) => {
let formuleType = R.path(["formule", "explanation", "name"])(root)
let targets = formuleType == "somme"
? R.pluck(
"dottedName",
R.path(["formule", "explanation", "explanation"])(root)
)
: (root.formule || root['non applicable si'] || root['applicable si']) ? [root.dottedName] : null,
names = targets ? R.reject(R.isNil)(targets) : []
let findAndEvaluate = name => evaluateNode(situationGate,parsedRules,findRuleByDottedName(parsedRules,name))
return R.map(findAndEvaluate,names)
}
export let collectMissingVariables = (groupMethod='groupByMissingVariable') => (situationGate, {root, parsedRules}) => {
return R.pipe(
R.curry(getObjectives)(situationGate),
R.chain( v =>
R.pipe(
collectNodeMissing,
R.flatten,
R.map(mv => [v.dottedName, mv])
)(v)
),
//groupBy missing variable but remove mv from value, it's now in the key
R.groupBy(groupMethod == 'groupByMissingVariable' ? R.last : R.head),
R.map(R.map(groupMethod == 'groupByMissingVariable' ? R.head : R.last))
// below is a hand implementation of above... function composition can be nice sometimes :')
// R.reduce( (memo, [mv, dependencyOf]) => ({...memo, [mv]: [...(memo[mv] || []), dependencyOf] }), {})
)(root, parsedRules)
}
export let nextSteps = (situationGate, flatRules, analysedSituation) => {
let impact = ([variable, objectives]) => R.length(objectives)
let missingVariables = collectMissingVariables('groupByMissingVariable')(situationGate, analysedSituation),
pairs = R.toPairs(missingVariables),
sortedPairs = R.sort(R.descend(impact), pairs)
return R.map(R.head, sortedPairs)
}
export let constructStepMeta = ({
titre,
question,
subquestion,
dottedName,
name,
}) => ({
// name: dottedName.split(' . ').join('.'),
name: dottedName,
// <Explicable/> ajoutera une aide au clic sur un icône [?]
// Son texte est la question s'il y en a une à poser. Sinon on prend le titre.
question: (
<Explicable label={question || name} dottedName={dottedName} lightBackground={true} />
),
title: titre || name,
subquestion,
// Legacy properties :
visible: true,
// helpText: 'Voila un peu d\'aide poto'
})
let isVariant = R.path(['formule', 'une possibilité'])
let buildVariantTree = (allRules, path) => {
let rec = path => {
let node = findRuleByDottedName(allRules, path),
variant = isVariant(node),
variants = variant && R.unless(R.is(Array), R.prop('possibilités'))(variant),
shouldBeExpanded = variant && true, //variants.find( v => relevantPaths.find(rp => R.contains(path + ' . ' + v)(rp) )),
canGiveUp = variant && !variant['choix obligatoire']
return Object.assign(
node,
shouldBeExpanded ?
{ canGiveUp,
children: variants.map(v => rec(path + ' . ' + v))
}
: null
)
}
return rec(path)
}
export let makeQuestion = flatRules => dottedName => {
let rule = findRuleByDottedName(flatRules, dottedName)
let inputQuestion = rule => ({
component: Input,
valueType: formValueTypes[rule.format],
attributes: {
inputMode: 'numeric',
placeholder: 'votre réponse',
},
suggestions: rule.suggestions,
})
let selectQuestion = rule => ({
component: Select,
valueType: formValueTypes[rule.format],
suggestions: rule.suggestions,
})
let selectAtmp = rule => ({
component: SelectAtmp,
valueType: formValueTypes[rule.format],
suggestions: rule.suggestions,
})
let binaryQuestion = rule => ({
component: Question,
choices: [
{ value: 'non', label: 'Non' },
{ value: 'oui', label: 'Oui' },
],
})
let multiChoiceQuestion = rule => ({
component: Question,
choices: buildVariantTree(flatRules, dottedName)
})
let common = constructStepMeta(rule)
return Object.assign(
common,
isVariant(rule) ?
multiChoiceQuestion(rule) :
rule.format == null ?
binaryQuestion(rule) :
typeof rule.suggestions == 'string' ?
(rule.suggestions == 'atmp-2017' ? selectAtmp(rule) : selectQuestion(rule)) :
inputQuestion(rule)
)
}