Modification des reducers pour bien prendre en compte les défauts

 Déplacement de 'fromConversation' vers rules.js
pull/138/head
mama 2017-11-24 18:55:15 +01:00
parent aba5248e58
commit 9b545ee583
5 changed files with 85 additions and 88 deletions

View File

@ -13,7 +13,6 @@
- motif . classique . saisonnier
- motif . contrat aidé
formule:
multiplication:
assiette: assiette cotisations sociales
@ -36,7 +35,7 @@
événement: aucun
motif: accroissement activité
contrat jeune vacances: non
assiette cotisations sociales: 1480
valeur attendue: 14.8

View File

@ -21,7 +21,6 @@ import Results from 'Components/Results'
state => ({
currentQuestion: state.currentQuestion,
foldedSteps: state.foldedSteps,
extraSteps: state.extraSteps,
themeColours: state.themeColours,
situationGate: state.situationGate,
targetNames: state.targetNames,
@ -65,7 +64,6 @@ export default class extends Component {
let {
foldedSteps,
extraSteps,
currentQuestion,
situationGate,
themeColours,
@ -109,14 +107,6 @@ export default class extends Component {
),
foldedSteps
),
extraSteps: R.map(
this.buildStep({ unfolded: true })(
situationGate,
targetNames,
inputInversions
),
extraSteps
),
textColourOnWhite: themeColours.textColourOnWhite
}}
/>

View File

@ -11,7 +11,7 @@ import Scroll from 'react-scroll'
})
export default class Conversation extends Component {
render() {
let {foldedSteps, currentQuestion, extraSteps, reinitalise, textColourOnWhite} = this.props
let {foldedSteps, currentQuestion, reinitalise, textColourOnWhite} = this.props
Scroll.animateScroll.scrollToBottom()
return (
@ -30,13 +30,13 @@ export default class Conversation extends Component {
</div>
}
{!currentQuestion &&
<Conclusion affiner={!R.isEmpty(extraSteps)}/>}
{ !R.isEmpty(extraSteps) &&
<Conclusion affiner={!R.isEmpty({})}/>}
{ !R.isEmpty({}) &&
<div id="foldedSteps">
<div className="header" >
<h3>Affiner votre situation</h3>
</div>
{extraSteps}
{}
</div>
}
<div id="currentQuestion">

View File

@ -5,6 +5,7 @@ import R 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/rémunération-travail/cotisations/ok/liste-taux.json'
@ -108,3 +109,16 @@ export let findRuleByDottedName = (allRules, dottedName) => {
Autres */
let isVariant = R.path(['formule', 'une possibilité'])
export let formatInputs = (flatRules, formValueSelector) => state => name => {
// 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, name),
format = rule ? formValueTypes[rule.format] : null,
pre = format && format.validator.pre ? format.validator.pre : R.identity,
value = formValueSelector('conversation')(state, name)
return value && pre(value)
}

View File

@ -1,32 +1,28 @@
import R from 'ramda'
import { combineReducers } from 'redux'
import reduceReducers from 'reduce-reducers'
import {reducer as formReducer, formValueSelector} from 'redux-form'
import { reducer as formReducer, formValueSelector } from 'redux-form'
import {rules, findRuleByName, findRuleByDottedName, collectDefaults} from 'Engine/rules'
import {nextSteps} from 'Engine/generateQuestions'
import {
rules,
findRuleByName,
collectDefaults,
nameLeaf,
formatInputs
} from 'Engine/rules'
import { nextSteps } from 'Engine/generateQuestions'
import computeThemeColours from 'Components/themeColours'
import { STEP_ACTION, START_CONVERSATION, EXPLAIN_VARIABLE, CHANGE_THEME_COLOUR} from './actions'
import {
STEP_ACTION,
START_CONVERSATION,
EXPLAIN_VARIABLE,
CHANGE_THEME_COLOUR
} from './actions'
import {analyse} from 'Engine/traverse'
import { analyse } from 'Engine/traverse'
import ReactPiwik from 'Components/Tracker'
import formValueTypes from 'Components/conversation/formValueTypes'
let fromConversation = flatRules => state => name => {
// 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, name),
format = rule ? formValueTypes[rule.format] : null,
pre = format && format.validator.pre ? format.validator.pre : R.identity,
value = formValueSelector('conversation')(state, name)
return value && pre(value)
}
// assume "wraps" a given situation function with one that overrides its values with
// the given assumptions
let assume = (evaluator, assumptions) => state => name => {
@ -34,73 +30,73 @@ let assume = (evaluator, assumptions) => state => name => {
return userInput != null ? userInput : assumptions[name]
}
export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) => {
if (![START_CONVERSATION, STEP_ACTION].includes(action.type))
return state
export let reduceSteps = (tracker, flatRules, answerSource) => (
state,
action
) => {
if (![START_CONVERSATION, STEP_ACTION].includes(action.type)) return state
let targetNames = action.type == START_CONVERSATION ? action.targetNames : state.targetNames
let targetNames =
action.type == START_CONVERSATION ? action.targetNames : state.targetNames
let sim = targetNames.length === 1 ? findRuleByName(flatRules, targetNames[0]) : {},
let sim =
targetNames.length === 1 ? findRuleByName(flatRules, targetNames[0]) : {},
// Hard assumptions cannot be changed, they are used to specialise a simulator
// before the user sees the first question
hardAssumptions = R.pathOr({},['simulateur','hypothèses'],sim),
hardAssumptions = R.pathOr({}, ['simulateur', 'hypothèses'], sim),
intermediateSituation = assume(answerSource, hardAssumptions),
// Most rules have default values
rulesDefaults = collectDefaults(flatRules),
situationWithDefaults = assume(intermediateSituation, rulesDefaults)
let situationGate = situationWithDefaults(state),
let
parsedRules = R.path(['analysis', 'parsedRules'], state),
analysis = analyse(parsedRules || flatRules, targetNames)(situationGate)
analysis = analyse(parsedRules || flatRules, targetNames)(situationWithDefaults(state)),
next = nextSteps(situationWithDefaults(state), flatRules, analysis),
assumptionsMade = !R.isEmpty(rulesDefaults),
done = next.length == 0,
currentQuestion =
done && assumptionsMade
? // The simulation is "over" - except we can now fill in extra questions
// where the answers were previously given default reasonable assumptions
do {
let
reanalysis = analyse(analysis.parsedRules, targetNames)(
intermediateSituation(state)
),
next = nextSteps(intermediateSituation(state), flatRules, reanalysis)
R.head(next)
}
: R.head(next)
let newState = {
...state,
targetNames,
analysis,
situationGate: situationGate,
extraSteps: [],
explainedVariable: null
situationGate: situationWithDefaults(state),
explainedVariable: null,
currentQuestion
}
if (action.type == START_CONVERSATION) {
let next = nextSteps(situationGate, flatRules, newState.analysis)
return {
...newState,
foldedSteps: [],
currentQuestion: R.head(next)
// when objectives change, reject theme from answered questions
foldedSteps: R.reject(name => targetNames.includes(nameLeaf(name)))(
state.foldedSteps
)
}
}
if (action.type == STEP_ACTION && action.name == 'fold') {
tracker.push(['trackEvent', 'answer', action.step+': '+situationGate(action.step)])
let foldedSteps = [...state.foldedSteps, state.currentQuestion],
next = nextSteps(situationGate, flatRules, newState.analysis),
assumptionsMade = !R.isEmpty(rulesDefaults),
done = next.length == 0
// The simulation is "over" - except we can now fill in extra questions
// where the answers were previously given default reasonable assumptions
if (done && assumptionsMade) {
let newSituation = intermediateSituation(state),
reanalysis = analyse(analysis.parsedRules, targetNames)(newSituation),
extraSteps = nextSteps(newSituation, flatRules, reanalysis)
tracker.push(['trackEvent', 'done', 'extra questions: '+extraSteps.length])
return {
...newState,
foldedSteps,
currentQuestion: R.head(extraSteps)
}
}
if (done) {
tracker.push(['trackEvent', 'done', 'no more questions'])
}
tracker.push([
'trackEvent',
'answer',
action.step + ': ' + situationWithDefaults(state)(action.step)
])
return {
...newState,
foldedSteps,
currentQuestion: R.head(next)
foldedSteps: [...state.foldedSteps, state.currentQuestion]
}
}
if (action.type == STEP_ACTION && action.name == 'unfold') {
@ -110,7 +106,9 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
let previous = state.currentQuestion,
// we fold it back into foldedSteps if it had been answered
answered = previous && answerSource(state)(previous) != undefined,
foldedSteps = answered ? R.concat(state.foldedSteps, [previous]) : state.foldedSteps
foldedSteps = answered
? R.concat(state.foldedSteps, [previous])
: state.foldedSteps
return {
...newState,
@ -120,13 +118,12 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
}
}
function themeColours(state = computeThemeColours(), {type, colour}) {
if (type == CHANGE_THEME_COLOUR)
return computeThemeColours(colour)
function themeColours(state = computeThemeColours(), { type, colour }) {
if (type == CHANGE_THEME_COLOUR) return computeThemeColours(colour)
else return state
}
function explainedVariable(state = null, {type, variableName=null}) {
function explainedVariable(state = null, { type, variableName = null }) {
switch (type) {
case EXPLAIN_VARIABLE:
return variableName
@ -135,17 +132,15 @@ function explainedVariable(state = null, {type, variableName=null}) {
}
}
export default reduceReducers(
combineReducers({
sessionId: (id = Math.floor(Math.random() * 1000000000000) + '') => id,
sessionId: (id = Math.floor(Math.random() * 1000000000000) + '') => id,
// this is handled by redux-form, pas touche !
form: formReducer,
/* Have forms been filled or ignored ?
false means the user is reconsidering its previous input */
foldedSteps: (steps = []) => steps,
extraSteps: (steps = []) => steps,
currentQuestion: (state = null) => state,
analysis: (state = null) => state,
@ -158,8 +153,7 @@ export default reduceReducers(
themeColours,
explainedVariable
}),
// cross-cutting concerns because here `state` is the whole state tree
reduceSteps(ReactPiwik, rules, fromConversation(rules))
reduceSteps(ReactPiwik, rules, formatInputs(rules, formValueSelector))
)