2017-04-24 18:03:38 +00:00
import React from 'react'
2017-05-18 14:04:23 +00:00
import Explicable from 'Components/conversation/Explicable'
2017-04-24 18:03:38 +00:00
import R from 'ramda'
2017-05-18 14:04:23 +00:00
import Question from 'Components/conversation/Question'
import Input from 'Components/conversation/Input'
import formValueTypes from 'Components/conversation/formValueTypes'
2017-04-28 15:03:34 +00:00
import { analyseSituation } from './traverse'
import { formValueSelector } from 'redux-form'
import { STEP _ACTION , START _CONVERSATION } from '../actions'
import { findGroup , findRuleByDottedName , parentName , collectMissingVariables , findVariantsAndRecords } from './rules'
export let reduceSteps = ( state , action ) => {
if ( ! [ START _CONVERSATION , STEP _ACTION ] . includes ( action . type ) )
return state
let rootVariable = action . type == START _CONVERSATION ? action . rootVariable : state . analysedSituation . name
let returnObject = {
... state ,
analysedSituation : analyse ( rootVariable ) ( state )
}
if ( action . type == START _CONVERSATION ) {
return {
... returnObject ,
2017-05-18 13:01:34 +00:00
foldedSteps : state . foldedSteps || [ ] ,
2017-04-28 15:03:34 +00:00
unfoldedSteps : buildNextSteps ( returnObject . analysedSituation )
}
}
if ( action . type == STEP _ACTION && action . name == 'fold' ) {
return {
... returnObject ,
foldedSteps : [ ... state . foldedSteps , R . head ( state . unfoldedSteps ) ] ,
unfoldedSteps : buildNextSteps ( returnObject . analysedSituation )
}
}
if ( action . type == STEP _ACTION && action . name == 'unfold' ) {
let stepFinder = R . propEq ( 'name' , action . step ) ,
2017-05-10 08:11:56 +00:00
foldedSteps = R . reject ( stepFinder ) ( state . foldedSteps )
if ( foldedSteps . length != state . foldedSteps . length - 1 )
throw 'Problème lors du dépliement d\'une réponse'
2017-04-28 15:03:34 +00:00
return {
... returnObject ,
foldedSteps ,
unfoldedSteps : [ R . find ( stepFinder ) ( state . foldedSteps ) ]
}
}
}
let situationGate = state =>
name => formValueSelector ( 'conversation' ) ( state , name )
let analyse = rootVariable => R . pipe (
situationGate ,
// une liste des objectifs de la simulation (des 'rules' aussi nommées 'variables')
analyseSituation ( rootVariable )
)
/ *
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 ] }
* /
let buildNextSteps = analysedSituation => {
let missingVariables = collectMissingVariables ( 'groupByMissingVariable' ) (
2017-05-07 17:45:44 +00:00
analysedSituation
2017-04-28 15:03:34 +00:00
)
2017-05-09 15:14:13 +00:00
2017-05-11 10:04:55 +00:00
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 _ * *
* /
return R . pipe (
R . keys ,
R . reduce (
findVariantsAndRecords
, { variantGroups : { } , recordGroups : { } }
) ,
// 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 ( {
variantGroups : generateGridQuestions ( missingVariables ) ,
recordGroups : generateSimpleQuestions ( missingVariables ) ,
} ) ,
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 ,
// question: question || name,
question : (
2017-04-28 15:47:01 +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é' ] )
let buildVariantTree = relevantPaths => path => {
let rec = path => {
let node = findRuleByDottedName ( 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-04-28 12:33:22 +00:00
export let generateGridQuestions = missingVariables => R . pipe (
2017-04-24 18:03:38 +00:00
R . toPairs ,
R . map ( ( [ variantRoot , relevantVariants ] ) =>
( {
... constructStepMeta ( findRuleByDottedName ( variantRoot ) ) ,
component : Question ,
choices : buildVariantTree ( relevantVariants ) ( variantRoot ) ,
2017-04-28 12:33:22 +00:00
objectives : R . pipe (
R . chain ( v => missingVariables [ v ] ) ,
R . uniq ( )
2017-05-11 10:04:55 +00:00
) ( 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-24 18:03:38 +00:00
} )
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-04-28 12:33:22 +00:00
export let generateSimpleQuestions = 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 => {
let rule = findRuleByDottedName ( dottedName )
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
}
)
} )
)