From 92fed2c520a46a8e7e9e92553d4e1eb4cde1cdf5 Mon Sep 17 00:00:00 2001 From: mama Date: Tue, 7 Nov 2017 19:46:40 +0100 Subject: [PATCH] =?UTF-8?q?:gear:=20Adapation=20du=20moteur=20=C3=A0=20la?= =?UTF-8?q?=20simulation=20multiple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/components/Results.js | 24 ++++---- source/components/Simulateur.js | 6 +- source/components/rule/Examples.js | 6 +- source/components/rule/Rule.js | 10 ++-- source/engine/generateQuestions.js | 45 ++++----------- source/engine/traverse.js | 78 ++++++++++++++----------- source/reducers.js | 25 ++++---- test/generateQuestions.test.js | 91 ++++++++++++------------------ test/inversion.test.js | 16 +++--- test/mecanisms.test.js | 11 ++-- test/reducers.test.js | 11 ++-- test/rules.test.js | 1 - test/traverse.test.js | 61 ++++++++++---------- 13 files changed, 181 insertions(+), 204 deletions(-) diff --git a/source/components/Results.js b/source/components/Results.js index e4615fb1e..02619eb1b 100644 --- a/source/components/Results.js +++ b/source/components/Results.js @@ -8,13 +8,13 @@ import { withRouter } from 'react-router' import './Results.css' import {clearDict} from 'Engine/traverse' import {encodeRuleName} from 'Engine/rules' -import {getObjectives} from 'Engine/generateQuestions' import RuleValueVignette from './rule/RuleValueVignette' @withRouter @connect( state => ({ - analysedSituation: state.analysedSituation, + analysis: state.analysis, + targetName: state.targetName, conversationStarted: !R.isEmpty(state.form), conversationFirstAnswer: R.path(['form', 'conversation', 'values'])(state), situationGate: state.situationGate @@ -23,37 +23,39 @@ import RuleValueVignette from './rule/RuleValueVignette' export default class Results extends Component { componentDidMount(){ setTimeout(() => - this.props.setElementHeight(this.el.offsetHeight) - , 1) + this.el && this.props.setElementHeight(this.el.offsetHeight) + , 1) } render() { let { - analysedSituation, + analysis, + targetName, conversationStarted, conversationFirstAnswer: showResults, - situationGate, location } = this.props - let explanation = R.has('root', analysedSituation) && clearDict() && getObjectives(situationGate, analysedSituation.root, analysedSituation.parsedRules) + if (!analysis) return null - if (!explanation) return null + let {targets} = analysis + + clearDict() // pourquoi ?? let onRulePage = R.contains('/regle/')(location.pathname) return (
this.el = el} id="results" className={classNames({show: showResults})}> {onRulePage && conversationStarted ?
- + Reprendre la simulation
:
-

{explanation.length == 1 ? 'Votre résultat' : 'Vos résultats'}·Cliquez pour comprendre chaque calcul

+

{targets.length == 1 ? 'Votre résultat' : 'Vos résultats'}·Cliquez pour comprendre chaque calcul

} diff --git a/source/components/Simulateur.js b/source/components/Simulateur.js index 9b83b85f6..58f01fb29 100644 --- a/source/components/Simulateur.js +++ b/source/components/Simulateur.js @@ -24,11 +24,10 @@ import ReactPiwik from './Tracker' foldedSteps: state.foldedSteps, extraSteps: state.extraSteps, themeColours: state.themeColours, - analysedSituation: state.analysedSituation, situationGate: state.situationGate, }), dispatch => ({ - startConversation: rootVariable => dispatch({type: START_CONVERSATION, rootVariable}), + startConversation: targetName => dispatch({type: START_CONVERSATION, targetName}), resetForm: () => dispatch(reset('conversation')) }) ) @@ -56,7 +55,8 @@ export default class extends Component { this.props.startConversation(name) } render(){ - if (!this.rule.formule) return + if (!this.rule.formule && !R.path(['simulateur', 'objectifs'], this.rule)) + return let {started} = this.state, diff --git a/source/components/rule/Examples.js b/source/components/rule/Examples.js index ea0fe6ec3..cec8c79f2 100644 --- a/source/components/rule/Examples.js +++ b/source/components/rule/Examples.js @@ -5,7 +5,7 @@ import { rules, disambiguateRuleReference } from "Engine/rules.js" -import { analyseSituation } from "Engine/traverse" +import { analyse } from "Engine/traverse" import "./Examples.css" export default class Examples extends Component { @@ -23,10 +23,10 @@ export default class Examples extends Component { R.fromPairs )(ex.situation) - let runExemple = analyseSituation(rules, rule.name)( + let runExemple = analyse(rules, rule.name)( v => exempleSituation[v] ), - exempleValue = runExemple.nodeValue + exempleValue = runExemple.targets[0].nodeValue return { ...ex, diff --git a/source/components/rule/Rule.js b/source/components/rule/Rule.js index af3ae2b34..fdbabdc2e 100644 --- a/source/components/rule/Rule.js +++ b/source/components/rule/Rule.js @@ -3,7 +3,7 @@ import { connect } from "react-redux" import R from "ramda" import "./Rule.css" import { rules, decodeRuleName, nameLeaf } from "Engine/rules.js" -import { analyseSituation } from "Engine/traverse" +import { analyse } from "Engine/traverse" import { START_CONVERSATION } from "../../actions" import possiblesDestinataires from "Règles/ressources/destinataires/destinataires.yaml" import { capitalise0 } from "../../utils" @@ -19,8 +19,8 @@ import {createMarkdownDiv} from 'Engine/marked' form: state.form }), dispatch => ({ - startConversation: rootVariable => - dispatch({ type: START_CONVERSATION, rootVariable }) + startConversation: targetName => + dispatch({ type: START_CONVERSATION, targetName }) }) ) export default class Rule extends Component { @@ -36,9 +36,9 @@ export default class Rule extends Component { } } setRule(name) { - this.rule = analyseSituation(rules, nameLeaf(decodeRuleName(name)))( + this.rule = analyse(rules, nameLeaf(decodeRuleName(name)))( this.props.situationGate - ) + ).targets[0] } componentWillMount() { let { match: { params: { name } } } = this.props diff --git a/source/engine/generateQuestions.js b/source/engine/generateQuestions.js index b1111e4c1..a68bf8c37 100644 --- a/source/engine/generateQuestions.js +++ b/source/engine/generateQuestions.js @@ -8,9 +8,9 @@ import Select from 'Components/conversation/select/Select' import SelectAtmp from 'Components/conversation/select/SelectTauxRisque' import formValueTypes from 'Components/conversation/formValueTypes' -import {findInversion} from './traverse' import {findRuleByDottedName} from './rules' -import {collectNodeMissing, evaluateNode} from './evaluation' +import {collectNodeMissing} from './evaluation' + /* COLLECTE DES VARIABLES MANQUANTES @@ -27,33 +27,8 @@ import {collectNodeMissing, evaluateNode} from './evaluation' 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 inversion = findInversion(situationGate, parsedRules, root) - - if (inversion){ - return [evaluateNode(situationGate, parsedRules, inversion.fixedObjectiveRule)] - } - - 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), +export let collectMissingVariables = targets => + R.pipe( R.chain( v => R.pipe( collectNodeMissing, @@ -62,17 +37,17 @@ export let collectMissingVariables = (groupMethod='groupByMissingVariable') => ( )(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)) + R.groupBy(R.last), + R.map(R.map(R.head)) // 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) -} + )(targets) -export let nextSteps = (situationGate, flatRules, analysedSituation) => { + +export let nextSteps = (situationGate, flatRules, analysis) => { let impact = ([variable, objectives]) => R.length(objectives) - let missingVariables = collectMissingVariables('groupByMissingVariable')(situationGate, analysedSituation), + let missingVariables = collectMissingVariables(analysis.targets), pairs = R.toPairs(missingVariables), sortedPairs = R.sort(R.descend(impact), pairs) diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 08dd29772..21c85b050 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -143,7 +143,7 @@ let fillVariableNode = (rules, rule) => parseResult => { variablePartialName = fragments.join(' . '), dottedName = disambiguateRuleReference(rules, rule, variablePartialName) - let jsx = (nodeValue) => ( + let jsx = nodeValue => ( ) @@ -353,7 +353,8 @@ let treat = (rules, rule) => rawNode => { let mecanisms = R.intersection(R.keys(rawNode), R.keys(knownMecanisms)) if (mecanisms.length != 1) { - console.log( // eslint-disable-line no-console + console.log( + // eslint-disable-line no-console 'Erreur : On ne devrait reconnaître que un et un seul mécanisme dans cet objet', mecanisms, rawNode @@ -424,17 +425,16 @@ export let findInversion = (situationGate, rules, rule) => { } export let treatRuleRoot = (rules, rule) => { - let evaluate = (situationGate, parsedRules, r) => { let inversion = findInversion(situationGate, parsedRules, r) if (inversion) { - let {fixedObjectiveValue, fixedObjectiveRule} = inversion - let - fx = x => evaluateNode( - n => (r.name === n || n === 'sys.filter') ? x : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ? - parsedRules, - fixedObjectiveRule - ).nodeValue, + let { fixedObjectiveValue, fixedObjectiveRule } = inversion + let fx = x => + evaluateNode( + n => (r.name === n || n === 'sys.filter' ? x : situationGate(n)), //TODO pourquoi doit-on nous préoccuper de sys.filter ? + parsedRules, + fixedObjectiveRule + ).nodeValue, tolerancePercentage = 0.00001, // cette fonction détermine la racine d'une fonction sans faire trop d'itérations nodeValue = uniroot( @@ -445,18 +445,23 @@ export let treatRuleRoot = (rules, rule) => { 100 ) - // si fx renvoie null pour une valeur numérique standard, disons 1000, on peut - // considérer que l'inversion est impossible du fait de variables manquantes - // TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit - return fx(1000) == null ? { - ...r, - nodeValue: null, - inversionMissingVariables: collectNodeMissing(evaluateNode( - n => (r.name === n || n === 'sys.filter') ? 1000 : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ? - parsedRules, - fixedObjectiveRule - )) - } : {...r, nodeValue} + // si fx renvoie null pour une valeur numérique standard, disons 1000, on peut + // considérer que l'inversion est impossible du fait de variables manquantes + // TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit + return fx(1000) == null + ? { + ...r, + nodeValue: null, + inversionMissingVariables: collectNodeMissing( + evaluateNode( + n => + r.name === n || n === 'sys.filter' ? 1000 : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ? + parsedRules, + fixedObjectiveRule + ) + ) + } + : { ...r, nodeValue } } let evolveRule = R.curry(evaluateNode)(situationGate, parsedRules), @@ -485,7 +490,7 @@ export let treatRuleRoot = (rules, rule) => { return { ...evaluated, nodeValue, isApplicable } } - let collectMissing = (rule) => { + let collectMissing = rule => { let { formule, isApplicable, @@ -498,8 +503,7 @@ export let treatRuleRoot = (rules, rule) => { return inversionMissingVariables } - let - condMissing = + let condMissing = val(notApplicable) === true ? [] : val(applicable) === false @@ -599,12 +603,20 @@ let evolveCond = (name, rule, rules) => value => { } } -export let analyseSituation = (rules, rootVariable) => situationGate => { - let { root } = analyseTopDown(rules, rootVariable)(situationGate) - return root +export let getTargets = (target, rules) => { + let multiSimulation = R.path(['simulateur', 'objectifs'])(target) + let targets = multiSimulation + ? // On a un simulateur qui définit une liste d'objectifs + multiSimulation + .map(n => disambiguateRuleReference(rules, target, n)) + .map(n => findRuleByDottedName(rules, n)) + : // Sinon on est dans le cas d'une simple variable d'objectif + [target] + + return targets } -export let analyseTopDown = (rules, rootVariable) => situationGate => { +export let analyse = (rules, targetName) => situationGate => { clearDict() let /* La fonction treatRuleRoot va descendre l'arbre de la règle `rule` et produire un AST, un objet contenant d'autres objets contenant d'autres objets... @@ -618,15 +630,17 @@ export let analyseTopDown = (rules, rootVariable) => situationGate => { parsedRules = R.map(treatOne, rules), // TODO: we should really make use of namespaces at this level, in particular // setRule in Rule.js needs to get smarter and pass dottedName - rootRule = findRuleByName(parsedRules, rootVariable), + parsedTarget = findRuleByName(parsedRules, targetName), /* Ce n'est que dans cette nouvelle étape que l'arbre est vraiment évalué. Auparavant, l'évaluation était faite lors de la construction de l'AST. */ - root = evaluateNode(situationGate, parsedRules, rootRule) + targets = getTargets(parsedTarget, parsedRules).map(t => + evaluateNode(situationGate, parsedRules, t) + ) return { - root, + targets, parsedRules } } diff --git a/source/reducers.js b/source/reducers.js index 9186e8ef4..a9777a3f5 100644 --- a/source/reducers.js +++ b/source/reducers.js @@ -9,7 +9,7 @@ import {nextSteps, makeQuestion} from 'Engine/generateQuestions' import computeThemeColours from 'Components/themeColours' import { STEP_ACTION, START_CONVERSATION, EXPLAIN_VARIABLE, CHANGE_THEME_COLOUR} from './actions' -import {analyseTopDown} from 'Engine/traverse' +import {analyse} from 'Engine/traverse' import ReactPiwik from 'Components/Tracker'; @@ -39,9 +39,9 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) = if (![START_CONVERSATION, STEP_ACTION].includes(action.type)) return state - let rootVariable = action.type == START_CONVERSATION ? action.rootVariable : state.analysedSituation.root.name + let targetName = action.type == START_CONVERSATION ? action.targetName : state.targetName - let sim = findRuleByName(flatRules, rootVariable), + let sim = findRuleByName(flatRules, targetName), // 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), @@ -51,18 +51,19 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) = completeSituation = assume(intermediateSituation,softAssumptions) let situationGate = completeSituation(state), - analysedSituation = analyseTopDown(flatRules,rootVariable)(situationGate) + analysis = analyse(flatRules, targetName)(situationGate) let newState = { ...state, - analysedSituation, + targetName, + analysis, situationGate: situationGate, extraSteps: [], explainedVariable: null } if (action.type == START_CONVERSATION) { - let next = nextSteps(situationGate, flatRules, newState.analysedSituation) + let next = nextSteps(situationGate, flatRules, newState.analysis) return { ...newState, @@ -74,7 +75,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) = tracker.push(['trackEvent', 'answer', action.step+": "+situationGate(action.step)]); let foldedSteps = [...state.foldedSteps, state.currentQuestion], - next = nextSteps(situationGate, flatRules, newState.analysedSituation), + next = nextSteps(situationGate, flatRules, newState.analysis), assumptionsMade = !R.isEmpty(softAssumptions), done = next.length == 0 @@ -82,10 +83,10 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) = // where the answers were previously given default reasonable assumptions if (done && assumptionsMade) { let newSituation = intermediateSituation(state), - reanalyse = analyseTopDown(flatRules,rootVariable)(newSituation), - extraSteps = nextSteps(newSituation, flatRules, reanalyse) + reanalysis = analyse(flatRules, targetName)(newSituation), + extraSteps = nextSteps(newSituation, flatRules, reanalysis) - tracker.push(['trackEvent', 'done', 'extra questions: '+extraSteps.length]); + tracker.push(['trackEvent', 'done', 'extra questions: '+extraSteps.length]) return { ...newState, @@ -154,7 +155,9 @@ export default reduceReducers( extraSteps: (steps = []) => steps, currentQuestion: (state = null) => state, - analysedSituation: (state = []) => state, + analysis: (state = null) => state, + + targetName: (state = null) => state, situationGate: (state = name => null) => state, refine: (state = false) => state, diff --git a/test/generateQuestions.test.js b/test/generateQuestions.test.js index 9ba341318..c00679383 100644 --- a/test/generateQuestions.test.js +++ b/test/generateQuestions.test.js @@ -1,27 +1,12 @@ import R from 'ramda' import {expect} from 'chai' import {rules as realRules, enrichRule} from '../source/engine/rules' -import {analyseTopDown} from '../source/engine/traverse' -import {nextSteps, collectMissingVariables, getObjectives} from '../source/engine/generateQuestions' +import {analyse} from '../source/engine/traverse' +import {nextSteps, collectMissingVariables} from '../source/engine/generateQuestions' + let stateSelector = (name) => null -describe('getObjectives', function() { - - it('should derive objectives from the root rule', function() { - let rawRules = [ - {nom: "startHere", formule: 2, "non applicable si" : "sum . evt . ko", espace: "sum"}, - {nom: "evt", espace: "sum", formule: {"une possibilité":["ko"]}, titre: "Truc", question:"?"}, - {nom: "ko", espace: "sum . evt"}], - rules = rawRules.map(enrichRule), - {root, parsedRules} = analyseTopDown(rules,"startHere")(stateSelector), - result = getObjectives(stateSelector, root, parsedRules) - - expect(result).to.have.lengthOf(1) - expect(result[0]).to.have.property('name','startHere') - }); - -}); describe('collectMissingVariables', function() { @@ -31,8 +16,8 @@ describe('collectMissingVariables', function() { {nom: "evt", espace: "sum", formule: {"une possibilité":["ko"]}, titre: "Truc", question:"?"}, {nom: "ko", espace: "sum . evt"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('sum . evt . ko') }); @@ -42,8 +27,8 @@ describe('collectMissingVariables', function() { {nom: "nope", espace: "sum . evt"}, {nom: "nyet", espace: "sum . evt"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('sum . evt . nyet') expect(result).to.have.property('sum . evt . nope') @@ -54,8 +39,8 @@ describe('collectMissingVariables', function() { {nom: "startHere", formule: "trois", "non applicable si" : "3 > 2", espace: "sum"}, {nom: "trois", espace: "sum"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -65,8 +50,8 @@ describe('collectMissingVariables', function() { {nom: "startHere", formule: "trois", "non applicable si" : {"une de ces conditions": ["3 > 2", "trois"]}, espace: "sum"}, {nom: "trois", espace: "sum"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -76,8 +61,8 @@ describe('collectMissingVariables', function() { {nom: "startHere", formule: "trois", espace: "top"}, {nom: "trois", formule: {"une possibilité":["ko"]}, espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('top . trois') }); @@ -87,8 +72,8 @@ describe('collectMissingVariables', function() { {nom: "startHere", formule: "trois", espace: "top"}, {nom: "trois", formule: {"une possibilité":["ko"]}, "non applicable si": 1, espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -100,8 +85,8 @@ describe('collectMissingVariables', function() { {nom: "startHere", formule: "trois", espace: "top"}, {nom: "trois", formule: {"une possibilité":["ko"]}, espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(mySelector), - result = collectMissingVariables()(mySelector,situation) + analysis = analyse(rules,"startHere")(mySelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -115,8 +100,8 @@ describe('collectMissingVariables', function() { }}, espace: "top"}, {nom: "dix", espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('top . dix') }); @@ -135,8 +120,8 @@ describe('collectMissingVariables', function() { {nom: "dix", espace: "top"}, {nom: "deux", espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('top . dix') // expect(result).to.have.property('top . deux') - this is a TODO @@ -156,8 +141,8 @@ describe('collectMissingVariables', function() { {nom: "dix", espace: "top"}, {nom: "deux", espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -170,8 +155,8 @@ describe('collectMissingVariables', function() { }}, espace: "top"}, {nom: "dix", espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -193,8 +178,8 @@ describe('collectMissingVariables', function() { { nom: "dix", espace: "top" } ], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules, "startHere")(stateSelector), - result = collectMissingVariables()(stateSelector, situation); + analysis = analyse(rules, "startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.have.property('top . dix') @@ -210,8 +195,8 @@ describe('collectMissingVariables', function() { }}, espace: "top"}, {nom: "dix", espace: "top"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"startHere")(stateSelector), - result = collectMissingVariables()(stateSelector,situation) + analysis = analyse(rules,"startHere")(stateSelector), + result = collectMissingVariables(analysis.targets) expect(result).to.deep.equal({}) }); @@ -227,8 +212,8 @@ describe('nextSteps', function() { {nom: "evt", espace: "top . sum", formule: {"une possibilité":["ko"]}, titre: "Truc", question:"?"}, {nom: "ko", espace: "top . sum . evt"}], rules = rawRules.map(enrichRule), - situation = analyseTopDown(rules,"sum")(stateSelector), - result = nextSteps(stateSelector, rules, situation) + analysis = analyse(rules,"sum")(stateSelector), + result = nextSteps(stateSelector, rules, analysis) expect(result).to.have.lengthOf(1) expect(result[0]).to.equal("top . sum . evt") @@ -236,10 +221,9 @@ describe('nextSteps', function() { it('should generate questions from the real rules', function() { let rules = realRules.map(enrichRule), - situation = analyseTopDown(rules,"surcoût CDD")(stateSelector), - objectives = getObjectives(stateSelector, situation.root, situation.parsedRules), - missing = collectMissingVariables()(stateSelector,situation), - result = nextSteps(stateSelector, rules, situation) + analysis = analyse(rules,"surcoût CDD")(stateSelector), + missing = collectMissingVariables(analysis.targets), + result = nextSteps(stateSelector, rules, analysis) // expect(objectives).to.have.lengthOf(4) @@ -268,10 +252,9 @@ describe('nextSteps', function() { let stateSelector = (name) => ({"contrat salarié . type de contrat":"CDI","entreprise . effectif":"50"})[name] let rules = realRules.map(enrichRule), - situation = analyseTopDown(rules,"Salaire")(stateSelector), - objectives = getObjectives(stateSelector, situation.root, situation.parsedRules), - missing = collectMissingVariables()(stateSelector,situation), - result = nextSteps(stateSelector, rules, situation) + analysis = analyse(rules,"Salaire")(stateSelector), + missing = collectMissingVariables(analysis.targets), + result = nextSteps(stateSelector, rules, analysis) expect(result[0]).to.equal("contrat salarié . salaire de base") expect(result[1]).to.equal("contrat salarié . temps partiel") diff --git a/test/inversion.test.js b/test/inversion.test.js index a7954c5d9..4c1024132 100644 --- a/test/inversion.test.js +++ b/test/inversion.test.js @@ -1,6 +1,6 @@ import { expect } from "chai" import { enrichRule } from "../source/engine/rules" -import { analyseTopDown, analyseSituation } from "../source/engine/traverse" +import { analyse } from "../source/engine/traverse" import { collectMissingVariables } from "../source/engine/generateQuestions" import yaml from "js-yaml" import dedent from "dedent-js" @@ -23,9 +23,9 @@ describe("inversions", () => { format: euro `, rules = yaml.safeLoad(rawRules).map(enrichRule), - analysis = analyseSituation(rules, "net")(stateSelector) + analysis = analyse(rules, "net")(stateSelector) - expect(analysis.nodeValue).to.be.closeTo(1771, 0.001) + expect(analysis.targets[0].nodeValue).to.be.closeTo(1771, 0.001) }) */ @@ -46,9 +46,9 @@ describe("inversions", () => { - net `, rules = yaml.safeLoad(rawRules).map(enrichRule), - analysis = analyseSituation(rules, "brut")(stateSelector) + analysis = analyse(rules, "brut")(stateSelector) - expect(analysis.nodeValue).to.be.closeTo(2000 / (77 / 100), 0.0001 * 2000) + expect(analysis.targets[0].nodeValue).to.be.closeTo(2000 / (77 / 100), 0.0001 * 2000) }) it("should handle inversions with missing variables", () => { @@ -71,10 +71,10 @@ describe("inversions", () => { `, rules = yaml.safeLoad(rawRules).map(enrichRule), stateSelector = name => ({ net: 2000 }[name]), - analysis = analyseTopDown(rules, "brut")(stateSelector), - missing = collectMissingVariables()(stateSelector, analysis) + analysis = analyse(rules, "brut")(stateSelector), + missing = collectMissingVariables(analysis.targets) - expect(analysis.root.nodeValue).to.be.null + expect(analysis.targets[0].nodeValue).to.be.null expect(missing).to.have.key("cadre") }) }) diff --git a/test/mecanisms.test.js b/test/mecanisms.test.js index b86049c57..83b86c705 100644 --- a/test/mecanisms.test.js +++ b/test/mecanisms.test.js @@ -6,7 +6,7 @@ import {expect} from 'chai' import {enrichRule} from '../source/engine/rules' -import {analyseTopDown} from '../source/engine/traverse' +import {analyse} from '../source/engine/traverse' import {collectMissingVariables} from '../source/engine/generateQuestions' import testSuites from './load-mecanism-tests' import R from 'ramda' @@ -24,15 +24,16 @@ describe('Mécanismes', () => let rules = suite.map(enrichRule), state = situation || {}, stateSelector = name => state[name], - analysis = analyseTopDown(rules, nom)(stateSelector), - missing = collectMissingVariables()(stateSelector,analysis) + analysis = analyse(rules, nom)(stateSelector), + missing = collectMissingVariables(analysis.targets), + target = analysis.targets[0] // console.log('JSON.stringify(analysis', JSON.stringify(analysis)) if (isFloat(valeur)) { - expect(analysis.root.nodeValue).to.be.closeTo(valeur,0.001) + expect(target.nodeValue).to.be.closeTo(valeur,0.001) } else if (valeur !== undefined) { - expect(analysis.root) + expect(target) .to.have.property( 'nodeValue', valeur diff --git a/test/reducers.test.js b/test/reducers.test.js index f9ec79e8f..67171f741 100644 --- a/test/reducers.test.js +++ b/test/reducers.test.js @@ -2,7 +2,6 @@ import R from 'ramda' import {expect} from 'chai' import {rules as realRules, enrichRule} from '../source/engine/rules' -import {analyseSituation, analyseTopDown} from '../source/engine/traverse' import {collectMissingVariables, getObjectives} from '../source/engine/generateQuestions' import {reduceSteps} from '../source/reducers' @@ -22,10 +21,10 @@ describe('fold', function() { {nom: "bb", question: "?", titre: "b", espace: "top"}], rules = rawRules.map(enrichRule), reducer = reduceSteps(tracker, rules, stateSelector), - action = {type:'START_CONVERSATION', rootVariable: 'startHere'}, + action = {type:'START_CONVERSATION', targetName: 'startHere'}, // situation = analyseTopDown(rules,"startHere")(stateSelector({})), // objectives = getObjectives(stateSelector({}), situation.root, situation.parsedRules), - // missing = collectMissingVariables()(stateSelector({}),situation), + // missing = collectMissingVariables(stateSelector({}),situation), result = reducer({},action) expect(result).to.have.property('currentQuestion') @@ -48,7 +47,7 @@ describe('fold', function() { rules = rawRules.map(enrichRule), reducer = reduceSteps(tracker, rules, stateSelector) - var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'}) + var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'}) fakeState['top . aa'] = 1 var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'}) fakeState['top . bb'] = 1 @@ -82,7 +81,7 @@ describe('fold', function() { rules = rawRules.map(enrichRule), reducer = reduceSteps(tracker, rules, stateSelector) - var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'}) + var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'}) fakeState['top . aa'] = 1 var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'}) @@ -116,7 +115,7 @@ describe('fold', function() { rules = rawRules.map(enrichRule), reducer = reduceSteps(tracker, rules, stateSelector) - var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'}) + var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'}) fakeState['top . aa'] = 1 var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'}) var step3 = reducer(step2,{type:'STEP_ACTION', name: 'unfold', step: 'top . bb'}) diff --git a/test/rules.test.js b/test/rules.test.js index 7e80eb072..6c82e5a81 100644 --- a/test/rules.test.js +++ b/test/rules.test.js @@ -1,7 +1,6 @@ import R from 'ramda' import {expect} from 'chai' import {rules, enrichRule, findVariantsAndRecords} from '../source/engine/rules' -import {analyseSituation, analyseTopDown} from '../source/engine/traverse' let stateSelector = (state, name) => null diff --git a/test/traverse.test.js b/test/traverse.test.js index 57c24f27b..f5c8ffedc 100644 --- a/test/traverse.test.js +++ b/test/traverse.test.js @@ -1,33 +1,34 @@ import {expect} from 'chai' import {rules as realRules, enrichRule} from '../source/engine/rules' -import {analyseSituation, analyseTopDown} from '../source/engine/traverse' +import {analyse, getTargets} from '../source/engine/traverse' let stateSelector = (state, name) => null -describe('analyseSituation', function() { + +describe('analyse', function() { it('should directly return simple numerical values', function() { let rule = {name: "startHere", formule: 3269} let rules = [rule] - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3269) }); it('should compute expressions combining constants', function() { let rule = {name: "startHere", formule: "32 + 69"} let rules = [rule] - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',101) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',101) }); }); -describe('analyseSituation on raw rules', function() { +describe('analyse on raw rules', function() { it('should handle direct referencing of a variable', function() { let rawRules = [ {nom: "startHere", formule: "dix", espace: "top"}, {nom: "dix", formule: 10, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',10) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',10) }); it('should handle expressions referencing other rules', function() { @@ -35,7 +36,7 @@ describe('analyseSituation on raw rules', function() { {nom: "startHere", formule: "3259 + dix", espace: "top"}, {nom: "dix", formule: 10, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3269) }); it('should handle applicability conditions', function() { @@ -44,7 +45,7 @@ describe('analyseSituation on raw rules', function() { {nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"}, {nom: "vrai", formule: "2 > 1", espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3259) }); it('should handle comparisons', function() { @@ -52,7 +53,7 @@ describe('analyseSituation on raw rules', function() { {nom: "startHere", formule: "3259 > dix", espace: "top"}, {nom: "dix", formule: 10, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',true) }); /* TODO: make this pass @@ -62,26 +63,26 @@ describe('analyseSituation on raw rules', function() { {nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"}, {nom: "vrai", formule: "1", espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3259) }); */ }); -describe('analyseSituation with mecanisms', function() { +describe('analyse with mecanisms', function() { it('should handle n-way "or"', function() { let rawRules = [ {nom: "startHere", formule: {"une de ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',true) }); it('should handle n-way "and"', function() { let rawRules = [ {nom: "startHere", formule: {"toutes ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',false) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',false) }); it('should handle switch statements', function() { @@ -93,14 +94,14 @@ describe('analyseSituation with mecanisms', function() { }}, espace: "top"}, {nom: "dix", formule: 10, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',11) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',11) }); it('should handle percentages', function() { let rawRules = [ {nom: "startHere", formule: "35%", espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',0.35) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',0.35) }); it('should handle sums', function() { @@ -108,14 +109,14 @@ describe('analyseSituation with mecanisms', function() { {nom: "startHere", formule: {"somme": [3200, "dix", 9]}}, {nom: "dix", formule: 10}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3219) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3219) }); it('should handle multiplications', function() { let rawRules = [ {nom: "startHere", formule: {"multiplication": {assiette:3259, plafond:3200, facteur:1, taux:1.5}}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800) }); it('should handle components in multiplication', function() { @@ -124,7 +125,7 @@ describe('analyseSituation with mecanisms', function() { composantes: [{taux:0.7}, {taux:0.8}] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800) }); it('should apply a ceiling to the sum of components', function() { @@ -133,7 +134,7 @@ describe('analyseSituation with mecanisms', function() { composantes: [{taux:0.7}, {taux:0.8}] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800) }); it('should handle progressive scales', function() { @@ -144,7 +145,7 @@ describe('analyseSituation with mecanisms', function() { "tranches":[{"en-dessous de":1, taux: 0.1},{de:1, "à": 2, taux: 1.2}, ,{"au-dessus de":2, taux: 10}] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80) }); it('should handle progressive scales with components', function() { @@ -158,7 +159,7 @@ describe('analyseSituation with mecanisms', function() { ] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80) }); it('should handle progressive scales with variations', function() { @@ -172,14 +173,14 @@ describe('analyseSituation with mecanisms', function() { ] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1800+80) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1800+80) }); it('should handle max', function() { let rawRules = [ {nom: "startHere", formule: {"le maximum de": [3200, 60, 9]}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3200) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3200) }); it('should handle complements', function() { @@ -187,7 +188,7 @@ describe('analyseSituation with mecanisms', function() { {nom: "startHere", formule: {complément: {cible: "dix", montant: 93}}, espace: "top"}, {nom: "dix", formule: 17, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',93-17) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',93-17) }); it('should handle components in complements', function() { @@ -197,7 +198,7 @@ describe('analyseSituation with mecanisms', function() { }}, espace: "top"}, {nom: "dix", formule: 17, espace: "top"}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',2*(93-17)) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',2*(93-17)) }); it('should handle filtering on components', function() { @@ -216,7 +217,7 @@ describe('analyseSituation with mecanisms', function() { ] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',50+400+40) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',50+400+40) }); it('should compute consistent values', function() { @@ -236,8 +237,8 @@ describe('analyseSituation with mecanisms', function() { ] }}}], rules = rawRules.map(enrichRule) - expect(analyseSituation(rules,"orHere")(stateSelector)).to.have.property('nodeValue',100+1200+80) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80) + expect(analyse(rules,"orHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80) }); it('should handle selection', function() { @@ -255,7 +256,7 @@ describe('analyseSituation with mecanisms', function() { données: 'taux_versement_transport'}, {espace: "top", nom: "code postal", format: "nombre"}], rules = rawRules.map(rule => enrichRule(rule,data)) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',0.02) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',0.02) }); it('should handle failed selections', function() { @@ -273,7 +274,7 @@ describe('analyseSituation with mecanisms', function() { données: 'taux_versement_transport'}, {espace: "top", nom: "code postal", format: "nombre"}], rules = rawRules.map(rule => enrichRule(rule,data)) - expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue', 0) + expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue', 0) }); });