diff --git a/règles/rémunération-travail/cdd/CIF.yaml b/règles/rémunération-travail/cdd/CIF.yaml index fb0add15a..1e036fed9 100644 --- a/règles/rémunération-travail/cdd/CIF.yaml +++ b/règles/rémunération-travail/cdd/CIF.yaml @@ -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 diff --git a/source/components/Simulateur.js b/source/components/Simulateur.js index 5bb0afa94..e9352044c 100644 --- a/source/components/Simulateur.js +++ b/source/components/Simulateur.js @@ -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 }} /> diff --git a/source/components/conversation/Conversation.js b/source/components/conversation/Conversation.js index 40254258e..f59eccb8b 100644 --- a/source/components/conversation/Conversation.js +++ b/source/components/conversation/Conversation.js @@ -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 { } {!currentQuestion && - } - { !R.isEmpty(extraSteps) && + } + { !R.isEmpty({}) &&

Affiner votre situation

- {extraSteps} + {}
}
diff --git a/source/engine/rules.js b/source/engine/rules.js index debdd8170..c89c3abaf 100644 --- a/source/engine/rules.js +++ b/source/engine/rules.js @@ -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) +} diff --git a/source/reducers.js b/source/reducers.js index ddef29b1f..7abeeb7d6 100644 --- a/source/reducers.js +++ b/source/reducers.js @@ -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)) )