2017-04-24 18:03:38 +00:00
import React from 'react'
import R from 'ramda'
2017-07-02 17:12:02 +00:00
import Explicable from 'Components/conversation/Explicable'
import Question from 'Components/conversation/Question'
import Input from 'Components/conversation/Input'
import formValueTypes from 'Components/conversation/formValueTypes'
2017-07-13 10:25:46 +00:00
import { analyseSituation } from './traverse'
2017-04-28 15:03:34 +00:00
import { formValueSelector } from 'redux-form'
2017-07-07 08:35:40 +00:00
import { rules , findRuleByDottedName , findVariantsAndRecords } from './rules'
2017-07-13 10:25:46 +00:00
import { collectNodeMissing , evaluateNode } from './evaluation'
2017-04-28 15:03:34 +00:00
let situationGate = state =>
name => formValueSelector ( 'conversation' ) ( state , name )
2017-07-07 08:41:06 +00:00
export let analyse = rootVariable => R . pipe (
2017-04-28 15:03:34 +00:00
situationGate ,
// une liste des objectifs de la simulation (des 'rules' aussi nommées 'variables')
2017-06-27 17:53:37 +00:00
analyseSituation ( rules , rootVariable )
2017-04-28 15:03:34 +00:00
)
/ *
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 ] }
* /
2017-07-07 08:35:40 +00:00
// On peut travailler sur une somme, les objectifs sont alors les variables de cette somme.
2017-08-16 13:47:12 +00:00
// Ou sur une variable unique ayant une formule ou une conodition 'non applicable si', elle est elle-même le seul objectif
2017-07-11 14:00:10 +00:00
export let getObjectives = ( situationGate , root , parsedRules ) => {
2017-08-16 13:47:12 +00:00
let formuleType = R . path ( [ "formule" , "explanation" , "name" ] ) ( root )
2017-07-09 22:14:22 +00:00
let targets = formuleType == "somme"
2017-07-07 08:35:40 +00:00
? R . pluck (
2017-07-09 22:14:22 +00:00
"dottedName" ,
R . path ( [ "formule" , "explanation" , "explanation" ] ) ( root )
2017-07-07 08:35:40 +00:00
)
2017-08-16 13:47:12 +00:00
: ( root . formule || root [ 'non applicable si' ] ) ? [ root . dottedName ] : null ,
2017-07-09 22:14:22 +00:00
names = targets ? R . reject ( R . isNil ) ( targets ) : [ ]
2017-07-07 08:35:40 +00:00
2017-07-11 14:00:10 +00:00
let findAndEvaluate = name => evaluateNode ( situationGate , parsedRules , findRuleByDottedName ( parsedRules , name ) )
return R . map ( findAndEvaluate , names )
2017-07-07 08:35:40 +00:00
}
2017-07-11 14:00:10 +00:00
export let collectMissingVariables = ( groupMethod = 'groupByMissingVariable' ) => ( situationGate , { root , parsedRules } ) => {
2017-07-09 22:14:22 +00:00
return R . pipe (
2017-07-11 14:00:10 +00:00
R . curry ( getObjectives ) ( situationGate ) ,
2017-07-07 08:35:40 +00:00
R . chain ( v =>
R . pipe (
2017-07-13 10:25:46 +00:00
collectNodeMissing ,
2017-07-07 08:35:40 +00:00
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] }), {})
2017-07-09 22:14:22 +00:00
) ( root , parsedRules )
}
2017-07-07 08:35:40 +00:00
2017-07-11 14:00:10 +00:00
export let buildNextSteps = ( situationGate , flatRules , analysedSituation ) => {
2017-04-28 15:03:34 +00:00
let missingVariables = collectMissingVariables ( 'groupByMissingVariable' ) (
2017-07-11 14:00:10 +00:00
situationGate , analysedSituation
2017-04-28 15:03:34 +00:00
)
/ *
Parmi les variables manquantes , certaines sont citées dans une règle de type 'une possibilité' .
* * On appelle ça des groupes de type 'variante' . * *
Il est alors plus intéressant de demander leur valeur dans un grille de possibilité plutôt que de façon indépendante .
Par exemple , au lieu de :
q1 : "Pensez vous prolonger le CDD en CDI" ,
r1 : Oui | Non
q2 : "Pensez-vous qu'une rupture pour faute grave est susceptible d'arriver"
r2 : Oui | Non
on préfère :
q : "Pensez-vous être confronté à l'un de ces événements ?"
r : Prolongation du CDD en CDI | Rupture pour faute grave .
Ceci est possible car ce sont tous les deux des événements et qu ' ils sont incompatibles entre eux .
Pour l 'instant, cela n' est possible que si les variables ont comme parent ( ou grand - parent ) ,
au sens de leur espace de nom , une règle de type 'une possibilité' .
# TODO pouvoir faire des variantes sans cette contrainte d ' espace de nom
D 'autres variables pourront être regroupées aussi, car elles partagent un parent, mais sans fusionner leurs questions dans l' interface . Ce sont des * * groupes de type _record _ * *
* /
2017-07-01 09:17:54 +00:00
2017-07-08 11:28:50 +00:00
// This is effectively a missingVariables.groupBy(questionRequired)
// but "questionRequired" does not have a clear specification
// we could look up "what formula is this variable mentioned in, and does it have a question attached"
// the problem is that we parse rules "bottom up", we would therefore need to:
// - parse rules top-down, i.e. analysedSituations = map(treatRuleRoot, rules)
// (might be a problem later on in terms of "big" rulesets, but not now)
// - decorate each rule with "mentions / depends on the following rules"
// - provide a "is mentioned by" query
2017-04-28 15:03:34 +00:00
return R . pipe (
R . keys ,
2017-07-11 14:00:10 +00:00
R . curry ( findVariantsAndRecords ) ( flatRules ) ,
2017-04-28 15:03:34 +00:00
// on va maintenant construire la liste des composants React qui afficheront les questions à l'utilisateur pour que l'on obtienne les variables manquantes
R . evolve ( {
2017-07-13 07:38:51 +00:00
variantGroups : generateGridQuestions ( flatRules , missingVariables ) ,
recordGroups : generateSimpleQuestions ( flatRules , missingVariables ) ,
2017-04-28 15:03:34 +00:00
} ) ,
R . values ,
R . unnest ,
2017-05-11 10:04:55 +00:00
R . sort ( ( a , b ) => b . impact - a . impact ) ,
2017-04-28 15:03:34 +00:00
) ( missingVariables )
}
2017-04-24 18:03:38 +00:00
export let constructStepMeta = ( {
titre ,
question ,
subquestion ,
dottedName ,
name ,
} ) => ( {
// name: dottedName.split(' . ').join('.'),
name : dottedName ,
2017-08-01 13:00:11 +00:00
// <Explicable/> ajoutera une aide au clic sur un icône [?]
2017-08-01 13:52:14 +00:00
// Son texte est la question s'il y en a une à poser. Sinon on prend le titre.
2017-04-24 18:03:38 +00:00
question : (
2017-08-01 13:52:14 +00:00
< Explicable label = { question || name } dottedName = { dottedName } lightBackground = { true } / >
2017-04-24 18:03:38 +00:00
) ,
title : titre || name ,
subquestion ,
// Legacy properties :
visible : true ,
// helpText: 'Voila un peu d\'aide poto'
} )
let isVariant = R . path ( [ 'formule' , 'une possibilité' ] )
2017-07-01 09:17:54 +00:00
let buildVariantTree = ( allRules , relevantPaths ) => path => {
2017-04-24 18:03:38 +00:00
let rec = path => {
2017-07-01 09:17:54 +00:00
let node = findRuleByDottedName ( allRules , path ) ,
2017-04-27 18:08:52 +00:00
variant = isVariant ( node ) ,
variants = variant && R . unless ( R . is ( Array ) , R . prop ( 'possibilités' ) ) ( variant ) ,
shouldBeExpanded = variant && variants . find ( v => relevantPaths . find ( rp => R . contains ( path + ' . ' + v ) ( rp ) ) ) ,
canGiveUp = variant && ! variant [ 'choix obligatoire' ]
2017-04-24 18:03:38 +00:00
return Object . assign (
node ,
shouldBeExpanded ?
2017-04-27 18:08:52 +00:00
{ canGiveUp ,
children : variants . map ( v => rec ( path + ' . ' + v ) )
}
2017-04-24 18:03:38 +00:00
: null
)
}
return rec ( path )
}
2017-07-01 09:17:54 +00:00
export let generateGridQuestions = ( allRules , missingVariables ) => R . pipe (
2017-04-24 18:03:38 +00:00
R . toPairs ,
2017-07-01 09:17:54 +00:00
R . map ( ( [ variantRoot , relevantVariants ] ) => {
return ( {
... constructStepMeta ( findRuleByDottedName ( allRules , variantRoot ) ) ,
component : Question ,
choices : buildVariantTree ( allRules , relevantVariants ) ( variantRoot ) ,
objectives : R . pipe (
R . chain ( v => missingVariables [ v ] ) ,
R . uniq ( )
) ( relevantVariants ) ,
// Mesure de l'impact de cette variable : combien de fois elle est citée par une règle
impact : relevantVariants . reduce ( ( count , next ) => count + missingVariables [ next ] . length , 0 )
} )
}
2017-04-28 12:33:22 +00:00
)
2017-04-24 18:03:38 +00:00
)
2017-05-02 14:53:56 +00:00
2017-07-01 09:17:54 +00:00
export let generateSimpleQuestions = ( allRules , missingVariables ) => R . pipe (
2017-04-24 18:03:38 +00:00
R . values , //TODO exploiter ici les groupes de questions de type 'record' (R.keys): elles pourraient potentiellement êtres regroupées visuellement dans le formulaire
R . unnest ,
R . map ( dottedName => {
2017-07-01 09:17:54 +00:00
let rule = findRuleByDottedName ( allRules , dottedName )
2017-04-24 18:03:38 +00:00
if ( rule == null ) console . log ( dottedName )
return Object . assign (
constructStepMeta ( rule ) ,
2017-05-02 14:53:56 +00:00
rule . format != null
2017-04-24 18:03:38 +00:00
? {
component : Input ,
2017-05-02 14:53:56 +00:00
valueType : formValueTypes [ rule . format ] ,
2017-04-24 18:03:38 +00:00
attributes : {
inputMode : 'numeric' ,
placeholder : 'votre réponse' ,
} ,
suggestions : rule . suggestions ,
}
: {
component : Question ,
choices : [
{ value : 'non' , label : 'Non' } ,
{ value : 'oui' , label : 'Oui' } ,
] ,
} ,
{
2017-04-28 12:33:22 +00:00
objectives : missingVariables [ dottedName ] ,
2017-05-11 10:04:55 +00:00
impact : missingVariables [ dottedName ] . length
2017-04-24 18:03:38 +00:00
}
)
} )
)